【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条边,应该不是通过暴搜+剪枝实现的,神题就拜着好了。