oyjpArt ACM/ICPC算法程序设计空间

// I am new in programming, welcome to my blog
I am oyjpart(alpc12, 四城)
posts - 224, comments - 694, trackbacks - 0, articles - 6

PKU1904 king's quest

Posted on 2007-08-07 23:58 oyjpart 阅读(2230) 评论(3)  编辑 收藏 引用 所属分类: ACM/ICPC或其他比赛

King's Quest
Time Limit:15000MS  Memory Limit:65536K
Total Submit:938 Accepted:278
Case Time Limit:2000MS

Description
Once upon a time there lived a king and he had N sons. And there were N beautiful girls in the kingdom and the king knew about each of his sons which of those girls he did like. The sons of the king were young and light-headed, so it was possible for one son to like several girls.

So the king asked his wizard to find for each of his sons the girl he liked, so that he could marry her. And the king's wizard did it -- for each son the girl that he could marry was chosen, so that he liked this girl and, of course, each beautiful girl had to marry only one of the king's sons.

However, the king looked at the list and said: "I like the list you have made, but I am not completely satisfied. For each son I would like to know all the girls that he can marry. Of course, after he marries any of those girls, for each other son you must still be able to choose the girl he likes to marry."

The problem the king wanted the wizard to solve had become too hard for him. You must save wizard's head by solving this problem.

Input
The first line of the input contains N -- the number of king's sons (1 <= N <= 2000). Next N lines for each of king's sons contain the list of the girls he likes: first Ki -- the number of those girls, and then Ki different integer numbers, ranging from 1 to N denoting the girls. The sum of all Ki does not exceed 200000.

The last line of the case contains the original list the wizard had made -- N different integer numbers: for each son the number of the girl he would marry in compliance with this list. It is guaranteed that the list is correct, that is, each son likes the girl he must marry according to this list.

Output
Output N lines.For each king's son first print Li -- the number of different girls he likes and can marry so that after his marriage it is possible to marry each of the other king's sons. After that print Li different integer numbers denoting those girls, in ascending order.

Sample Input

4
2 1 2
2 1 2
2 2 3
2 3 4
1 2 3 4

 

Sample Output

2 1 2
2 1 2
1 3
1 4

 

Hint
This problem has huge input and output data,use scanf() and printf() instead of cin and cout to read data to avoid time limit exceed.

Source
Northeastern Europe 2003


题目给我们一个2*N个顶点的2分图,并且给了一个完美匹配(Perfect Matching)以及每个顶点可以连接的其他的顶点。
题目要求是否可以确定某2个顶点连边后,其他2*(N - 1)个顶点的2分图是否可以构成完美匹配。
分析:
题目给了你一个初始的最大匹配 怎样用好这个匹配是非常关键的
首先把图转化成有向图(因为后面我们要用到连通性判定)
现在令王子为A集合,公主为B集合
原图中所有的边转化成A->B的边 然后对原来的每一个匹配Ai->Bj 添加一条从Bj到Ai的边
结论: 如果Ai,Bj能够互访 我们则认为Ai, Bj作为一条匹配边仍然不会影响其他王子和自己心爱的人匹配
证明如下:
现在有一个匹配Ai->Bj 我们知道Ai和Bj可以互访
我们要验证其是否可以成立时对其他王子找MM没有影响
1。如果题目中给的初始匹配包含这条边 则题目给出的初始匹配就证明了这位王子的公德心
2。如果题目没有给出这条边 给出的是Ai -> Bk 由于Ai与Bk也可互访 所以存在Bj->Ai->Bk的增广路 也就是说可以建立另外一个匹配A?->Bk
所以同一个连通分量内部是可以换匹配的 呵呵

关于求极大连通子图的方法 具体如下:
求有向图的极大强连通分支
1.对图进行DFS遍历 遍历中记下所有的结束时间A[i].遍历的结果是构建的一座森林W1
  我们对W1中的每棵树G进行步骤2到步骤3的操作
2.改变图G中每一条边的方向 构造出新的有向图Gr
3.按照A[i]由小到大的顺序对Gr进行DFS遍历.遍历的结果是构建了新的树林W2.
  W2中每棵树上的顶点构成了有向图的极大强连通分支
如果对DFS的相关算法不熟悉 请参考我的另一篇文章
图的DFS信息构建+割点,桥,极大连通子图三大法宝
http://www.cppblog.com/sicheng/archive/2007/01/19/17767.html

如果觉得上面的方法太麻烦 还有一种简单的方法(呵呵), 具体步骤如下:
我们定义定点U的标值函数 LOW(U) = min { dfn(U), dfn(W) };
其中W是U或者U的后代点通过反向边或者横叉边能够到达的同一个连通分量的定点
dfn()是指一个点第一次被访问的时间
由此可以看出 LOW(U)正是U所处的强连通分支中从U出发先通过树枝边(组成DFS树的边),前向边,最后用后向边或者横叉边到达的dfn的最小的定点的标值.而对于强分支的根Ri,显然LOW(Ri) = dfn(Ri). 因此当深度有限搜索从一个使dfn = LOW的定点U返回时,从树中移去根为U的所有顶点.每一个这种的集合是一个强分支.
算法的主要思路是逐步迭代算出LOW值:
当第一次访问定点U时:
LOW(U) = Min(LOW(U), dfn(U))
后向边(U,W)被检查时:
LOW(U) = Min(LOW(U), dfn(W))
处于同一强分支的横叉边被检查时:
LOW(U) = MIN(LOW(U), dfn(W))
检查了U的儿子S的所有关联边后返回顶点U时:
LOW(U) = Min(LOW(U), LOW(S))

这样就可以用一个DFS解决啦~ 

foj的一个"信与信封"的题目也可以用强连通来做 不过那个题目数据弱多了 枚举+2分图最大匹配也能过

下面这个程序是用第二种求法解决的:
Solution:
//by oyjpArt
 1#include <vector>
 2#include <algorithm>
 3using namespace std;
 4
 5const int N = 2010;
 6int nv, times, sccId;
 7int go[N], back[N]; //匹配的正向和反向
 8int low[N], dfn[N]; //low数组, dfn是第一次访问某节点的时间
 9int love[N][N];  // love[i][j]代表i王子爱上j公主 即i向j连接一条边
10int scc[N];   //scc代表强连通分量id (Strongly Connected Component ID)
11bool inS[N]; //inS代表是否在栈中(已被压入且为被弹出) 注意 表示一个节点没有被访问过应该是dfn[i] == 0
12vector<int> S; //Stack
13
14#define Min(a, b) ((a) < (b) ? (a) : (b))
15
16void DFS(int x) {
17    int i;
18    dfn[x] = ++times; //第一次访问
19    S.push_back(x); //压入栈中
20    inS[x] = 1//标记入栈
21    int y = back[x], z; //找到相应王子
22    low[x] = times; //定义low[x]
23
24    for(i = 1; i <= love[y][0]; ++i) {
25        int j = love[y][i];
26        if(j != x) {
27            if(dfn[j] == 0) { //树枝边
28                DFS(j); 
29                low[x] = Min(low[x], low[j]); //检查了x的儿子j的所有关联边后返回顶点x
30            }
31            else if(dfn[j] < dfn[x] && inS[j]) //处于同一强分支的后向边或者横叉边被检查
32                low[x] = Min(low[x], dfn[j]);
33        }
34    }
35
36    if(low[x] == dfn[x]) { //找到一个新的强连通分支
37        do {
38            z = S.back();
39            scc[z] = sccId; //标记连通分值 
40            inS[z] = false//出栈
41            S.pop_back();
42        }while(z != x); 
43        sccId++;
44    }
45}
46
47int main() {
48
49    scanf("%d"&nv);
50    int i, t, j;
51    for(i = 0; i < nv; ++i) {
52        scanf("%d"&love[i][0]);
53        for(j = 1; j <= love[i][0]; ++j) {
54            scanf("%d"&love[i][j]);
55            --love[i][j];
56        }
57    }
58    for(i = 0; i < nv; ++i) {
59        scanf("%d"&t);
60        go[i] = t-1;
61        back[t-1= i;
62    }
63
64    times = sccId = 0;
65    memset(inS, 0, sizeof(inS));
66    memset(dfn, 0, sizeof(dfn));
67    for(i = 0; i < nv; ++i) if(dfn[i] == 0) {
68        DFS(i); //对每个公主作DFS
69    }
70
71    for(i = 0; i < nv; ++i) {
72        vector<int> ans;
73        for(j = 1; j <= love[i][0]; ++j) {
74            if(scc[love[i][j]] == scc[go[i]]) //如果在同一个连通分量内
75                ans.push_back(love[i][j]);
76        }
77        sort(ans.begin(), ans.end());
78        printf("%d", ans.size());
79        for(j = 0; j < ans.size(); ++j)
80            printf(" %d", ans[j]+1);
81        printf("\n");
82    }
83
84    return 0;
85}


下面这种求法是用第一种方法解决的
Solution:
//by oyjpArt
 1#include <vector>
 2#include <algorithm>
 3using namespace std;
 4
 5const int N = 2010;
 6int nv;
 7vector<int> head[N], head2[N], S;
 8int go[N], back[N]; 
 9int scc[N];
10bool chk[N];
11bool love[N][N];
12
13void DFS(int x) {
14    int i;
15    chk[x] = 1;
16    for(i = 0; i < head[x].size(); ++i) {
17        int j = back[head[x][i]];
18        if(!chk[j]) 
19            DFS(j);    
20    }
21    S.push_back(x); //入栈
22}
23
24void DFS2(int x, int id) {
25    int y = go[x], i;
26    chk[y] = 1;
27    scc[x] = id; //标记连通分支
28    for(i = 0; i < head2[y].size(); ++i) {
29        int j = go[head2[y][i]]; //找到对应的公主
30        if(!chk[j])
31            DFS2(head2[y][i], id);
32    }
33}
34
35int main() {
36    scanf("%d"&nv);
37    int i, t, u, j;
38    for(i = 0; i < nv; ++i) {
39        scanf("%d"&t);
40        while(t--) {
41            scanf("%d"&u);
42            love[i][u-1= 1;
43            head[i].push_back(u-1); //有向边
44            head2[u-1].push_back(i); //逆转的有向边
45        }
46    }
47    for(i = 0; i < nv; ++i) {
48        scanf("%d"&t);
49        go[i] = t-1;
50        back[t-1= i;
51    }
52
53    memset(chk, 0, sizeof(chk));
54    for(i = 0; i < nv; ++i) if(!chk[i]) {
55        DFS(i); //对王子作DFS 确定i到达的点
56    }
57
58    memset(chk, 0, sizeof(chk));
59    int sccId = 0;
60    for(i = S.size()-1; i >= 0--i) {
61        int j = S[i];
62        if(!chk[go[j]]) {
63            DFS2(j, sccId); //再对公主做DFS 确定连通分支(对王子和对公主其实是一样的 写法有点不同而已:)
64            sccId++;
65        }
66    }
67
68    for(i = 0; i < nv; ++i) {
69        vector<int> ans;
70        for(j = 0; j < nv; ++j) if(love[i][j]) {
71            if(scc[i] == scc[back[j]])
72                ans.push_back(j);
73        }
74        sort(ans.begin(), ans.end());
75        printf("%d", ans.size());
76        for(j = 0; j < ans.size(); ++j)
77            printf(" %d", ans[j]+1);
78        printf("\n");
79    }
80
81    return 0;
82}

突然发现简单的方法写出来的程序还长一点点。。晕

Feedback

# re: PKU1904 king's quest  回复  更多评论   

2008-08-17 13:34 by sdfond
第二种思路很不错,学习下。。。十分感谢!~

# re: PKU1904 king's quest  回复  更多评论   

2009-07-01 12:52 by WinterLegend
写得不错,赞一个。

# re: PKU1904 king's quest  回复  更多评论   

2009-07-02 20:54 by oyjpart
谢谢

只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理