poj1112 Team Them Up! 二分着色 + DP

Team Them Up!

题意:简单地说,就是,一个N个节点的有向图,将节点分成两个集合,满足以下四个条件:

1。每个节点属于其中一个集合

2。每个集合至少有一个节点

3。集合里的每一个节点都有边连向同一个集合里的其他点

4。被分成的两个集合的大小要尽量接近

如果不能满足上述条件,输出 No solution ,否则输出这两个集合

 

刚开始也不知道怎么做,后来看了分析,其实弄懂了也不是太难,我还WA了这么多次,杯具!很不错的一道题啊,呵呵。。值得一练 !

说重点:

首先,求原图的补图,同时把有向图转化为无向图,即:对于节点u, v,如果没有边<u, v>或<v, u>,则建无向边(u, v)

分析一下现在这个图,如果u, v相连,表明u, v不能在同一个集合里,对于这个问题我们就有了感觉,先求图的连通分量,对于同一个连通分量,我们用二分图着色法,把整个连通分量里的点分到两个集合里,当然,如果着色失败,则无解,输出 No solution ,否则,这m个连通分量就构成了m个集合对(xi, yi),xi表示第i个连通分量中着色为0的集合,yi表示第i个连通分量中着色为1的集合,这样问题就转化成:对这m个集合对,每对里选出一个集合,构成A集合,剩余的构成B集合,要求A,B大小尽量接近,这样我们就可以用动态规划来搞定了。

DP方程:dp[i][j] = (dp[i][j - cnt[i][0]] | dp[i][j - cnt[i][1]] ) (1 <= i <= scc, 0 <= j <= N / 2)

dp[i][j] = 1 表示前i个连通分支,可以构成符合要求的节点数为j的集合

这里暂且用scc表示连通分支个数,N表示总节点个数,cnt[i][0]表示第i个分支中被着色成0的节点个数,cnt[i][1]表示第i个分支中被着色成1的节点个数,同时记录dp路径,这样这道题就算彻底搞定了。。 呵呵

 

代码
 
   
1 #include < stdlib.h >
2 #include < stdio.h >
3 #include < string .h >
4   #define MM 20010
5   #define NN 105
6
7 typedef struct node{
8 int v;
9 struct node * nxt;
10 }NODE;
11 NODE edg[MM], * Link[NN];
12
13   int idx, N, time, scc;
14 char map[NN][NN];
15 char col[NN];
16 int dfn[NN];
17 int cnt[NN][ 2 ];
18 int ans[NN][ 2 ][NN]; // 记录连通分支
19 char dp[NN][NN];
20 char pre[NN][NN]; // 记录dp路径
21 char flag[NN];
22
23 void Add( int u, int v){ // 建边
24 edg[idx].v = v;
25 edg[idx].nxt = Link[u];
26 Link[u] = edg + idx ++ ;
27 edg[idx].v = u;
28 edg[idx].nxt = Link[v];
29 Link[v] = edg + idx ++ ;
30 }
31
32 int dfs( int u){ // 着色过程
33 int v;
34 dfn[u] = ++ time;
35 ans[scc][col[u]][ ++ cnt[scc][col[u]]] = u;
36 for (NODE * p = Link[u]; p; p = p -> nxt){
37 v = p -> v;
38 if (dfn[v] == 0 ){
39 col[v] = ! col[u];
40 if (dfs(v) == 0 ) return 0 ; //
41 } else {
42 if (v != u && col[v] == col[u]) return 0 ;
43 }
44 }
45 return 1 ;
46 }
47
48 void Dp(){
49 int i, j, t;
50 dp[ 0 ][ 0 ] = 1 ;
51 for (i = 1 ; i <= N; i ++ ) dp[ 0 ][i] = 0 ;
52
53 for (i = 1 ; i <= scc; i ++ ){
54 for (j = 0 ; j <= N / 2 ; j ++ ){ // j从0开始
55 dp[i][j] = 0 ;
56 t = j - cnt[i][ 0 ];
57 if (t >= 0 && dp[i - 1 ][t]){
58 dp[i][j] = 1 ;
59 pre[i][j] = 0 ;
60 }
61 t = j - cnt[i][ 1 ];
62 if (t >= 0 && dp[i - 1 ][t]){
63 dp[i][j] = 1 ;
64 pre[i][j] = 1 ;
65 }
66 }
67 }
68 }
69
70 void Print(){ // 输出
71 int t, tmp, i, j, k;
72 for (k = N / 2 ; k >= 1 ; k -- ){
73 if (dp[scc][k]) break ;
74 }
75 if (k == 0 ) puts( " No solution " );
76 else {
77 int tmp = k;
78 memset(flag, 0 , sizeof (flag));
79 for (i = scc; i >= 1 ; i -- ){
80 t = pre[i][tmp];
81 tmp = tmp - cnt[i][t];
82 for (j = 1 ; j <= cnt[i][t]; j ++ ){
83 flag[ans[i][t][j]] = 1 ;
84 }
85 }
86 printf( " %d " , N - k);
87 for (i = 1 ; i <= N; i ++ ){
88 if (flag[i] == 0 ) printf( " %d " , i);
89 }
90 puts( "" );
91 printf( " %d " , k);
92 for (i = 1 ; i <= N; i ++ ){
93 if (flag[i]) printf( " %d " , i);
94 }
95 puts( "" );
96 }
97 }
98 void Solve(){
99 int i;
100 memset(dfn, 0 , sizeof (dfn)); // 发现标号
101 memset(col, 0 , sizeof (col));
102 memset(cnt, 0 , sizeof (cnt));
103 time = scc = 0 ;
104 for (i = 1 ; i <= N; i ++ ){
105 if (dfn[i] == 0 ){
106 ++ scc;
107 if (dfs(i) == 0 ) break ;
108 }
109 }
110 if (i <= N) puts( " No solution " );
111 else {
112 Dp();
113 Print();
114 }
115 }
116 int main()
117 {
118 int i, a, j;
119 scanf( " %d " , & N);
120
121 idx = 0 ; memset(Link, 0 , sizeof (Link));
122 memset(map, 0 , sizeof (map));
123 for (i = 1 ; i <= N; i ++ ){
124 while (scanf( " %d " , & a), a){
125 map[i][a] = 1 ;
126 }
127 }
128 for (i = 1 ; i <= N; i ++ ){
129 for (j = i + 1 ; j <= N; j ++ ){
130 if (map[i][j] == 0 || map[j][i] == 0 ) Add(i, j);
131 }
132 }
133 Solve();
134 return 0 ;
135 }
136

 

转载于:https://www.cnblogs.com/ylfdrib/archive/2010/10/08/1845769.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值