【HDOJ 3267】Graph Game

题目链接

给你一个图,RB两个玩家轮流给未涂色的边涂色,R涂红,B涂蓝,R先;B玩家的目的是让所有的点都能通过蓝边连通,R玩家则阻止B玩家成功,问你哪个玩家能获胜。

做了这题真心的感觉以前做的那么多搜索里的剪枝都弱爆了,套上博弈外壳的这些搜索题因为有了这么个环境,剪枝就更加帅气了:有哪些新奇的剪枝,什么剪枝可以剪,什么不能剪,比搜索里面的剪枝更有技巧性了。

刚开始只加了Alpha_Beta剪枝和记忆化的代码就狂交,TLE到死,肯定是过不了的。

剪枝一:如果两个点已经被蓝边连通了,那么无论是对于R玩家还是B玩家,再去涂他们之间的边是毫无意义的,这就砍掉很多状态了,从30!剪到了远小于10!。判连通就用并查集,每次做一遍略慢,而并查集不能删边,所以只能每层一个状态直接传下去好了。

剪枝二:对于平行边,可以认为B玩家一定可以连接两个点,B玩家只要在边被A全部删光前来插一脚,R玩家就前功尽弃了,R玩家也没那么傻,对平行边不会去插手的。所以,对于平行边,我们就可以提前认为B玩家已经连通了,直接用并查集把他们并起来好了。这个剪枝把剪枝一最棘手的平行边给砍掉了,还有什么更好的呢?

剪枝二扩展:我们可以认为并查集中在一个集合里面的点可以缩为同一个点,之后的操作就是在一个缩点后的新图上的博弈,对缩点后的新图继续做剪枝二的操作,直到不能做为止,然后在新图上继续做博弈。传递的状态又多了一个新图的二维数组,把剪枝二进行到底了。

有了上面的剪枝,直接0ms了,另外想过的一些连通性剪枝啊,桥边剪枝啊什么的也都没必要加了,哈希也没意义了,码:

 1 #include<cstdio>
 2 #include<cstring>
 3 #include<algorithm> 
 4 using namespace std;
 5 #define LL long long
 6 
 7 int n, m; 
 8 int l[32], r[32];
 9 bool red[32], blue[32];
10 
11 int find(int idx[10], int x){
12     if(idx[x]==-1) return x;
13     return idx[x]=find(idx, idx[x]); 
14 }
15 
16 void merge(int gra[10][10],int idx[10], int x, int y){
17     int rx = find(idx, x);
18     int ry = find(idx, y);
19     if(rx==ry) return;
20     idx[rx] = ry;
21     
22     for(int k=0;k<n;k++){
23         if(~idx[k]) continue;
24         if(k==ry) continue;
25         gra[ry][k] += gra[rx][k];
26         gra[k][ry] = gra[ry][k];
27     }
28 }
29 
30 int condense(int gra[10][10],int idx[10]){
31     bool flag=true;
32     while(flag){
33         flag = false;
34         for(int i=0;i<n;i++){
35             if(~idx[i]) continue;
36             for(int j=i+1;j<n;j++){
37                 if(~idx[j]) continue;
38                 if(gra[i][j]>=2){
39                     merge(gra, idx, i, j);
40                     flag = true;
41                 }
42             }
43         }
44     }
45     
46     int cnt=0; 
47     for(int i=0;i<n;i++){
48         if(~idx[i]) continue; 
49         cnt++;
50     }
51     return cnt; 
52 }
53 
54 int minmax(int deg, int red, int gra[10][10], int idx[10]){
55     bool end = true;
56     for(int i=0;i<m;i++){
57         if(red&(1<<i)) continue;
58         int rl = find(idx, l[i]);
59         int rr = find(idx, r[i]); 
60         if(rl==rr) continue;
61         end = false;
62         int tidx[10], tgra[10][10];
63         memcpy(tidx, idx, sizeof(tidx));
64         memcpy(tgra, gra, sizeof(tgra));
65         if(deg&1){
66             merge(tgra, tidx, rl, rr);
67             if(condense(tgra, tidx)==1) return 1;
68             if(minmax(deg+1, red, tgra, tidx)==1) return 1; 
69         } else { 
70             tgra[rl][rr] = tgra[rr][rl] = 0; 
71             if(minmax(deg+1, red|(1<<i), tgra, tidx)==-1) return -1;
72         }
73     } 
74     if(end) return -1;
75     if(0==(deg&1)) return 1;
76     if(1==(deg&1)) return -1;
77 } 
78 
79 int gra[10][10], idx[10];
80 int main(){
81     while(scanf("%d%d", &n,&m), ~n){
82         memset(gra, 0, sizeof(gra));
83         memset(idx, -1, sizeof(idx));
84         
85         for(int i=0;i<m;i++){
86             scanf("%d%d", &l[i], &r[i]); 
87             if(l[i]^r[i]){
88                 gra[l[i]][r[i]] ++;
89                 gra[r[i]][l[i]] ++;
90             }
91         }
92         if(condense(gra, idx)==1||minmax(0, 0, gra, idx)==1) puts("YES");
93         else puts("NO"); 
94     } 
95 } 

另外UVA 12370这题看起来类似,是要R玩家指定两个点,然后B玩家去连通这两个点,但有100个点,300条边,应该不是通过暴搜+剪枝实现的,神题就拜着好了。

posted @ 2012-07-23 14:54  Amb@HDU  阅读(872)  评论(0编辑  收藏  举报