[Noi2011]兔兔与蛋蛋
题目
题解
容易想到空格移动的路径是不会自交的。
因为空格移动的路径是黑白棋相间的
所以对棋盘进行黑白染色,建立二分图
如果黑白两格上的棋子不一样则可以连边
如果一个人(A)将空格移入了一个在最大匹配内的点,那么它的对手(B)就可以沿着匹配边前进(否则就相当于找到了一条新的匹配边)
而A只能沿着非匹配边前进,这样到最后肯定是A无路可走(否则就相当于找到了一条新的匹配边)
所以如果起始点不在最大匹配中那么先手只能走非匹配边,即必败。
另外有可能起始点在最大匹配中,但后手可以沿着另一个最大匹配前进。
所以必须保证起始点必须在最大匹配中。
具体的话可以给这个点打个删除标记,看看它的原匹配点能不能找到另一个对象。
形成代码就是先跑一边匈牙利
然后对于每个操作(把兔兔与蛋蛋的拆开),把经过的点全部删除,看看空格是否必须在最大匹配中。
注意
每执行完一次操作要清vis
黑白染色时相邻行要错开
#include <iostream> #include <cstring> #include <cstdio> #include <vector> #include <cmath> using namespace std; #define N 2000 #define pos(i,j) ((i-1)*m+j) char map[50][50]; vector<int> vec[N]; int mx[4]={0,0,1,-1},my[4]={1,-1,0,0},p[N],vis[N],match[N],win[N],n,m; bool disable[N]; void build(int i,int j) { for(int k=0;k<4;k++) { int tx=i+mx[k],ty=j+my[k]; if(abs(map[i][j]-map[tx][ty])==1) vec[pos(i,j)].push_back(pos(tx,ty)); } } bool dfs(int id) { for(int i=0;i<vec[id].size();i++) { int to=vec[id][i]; if(vis[to]||disable[to]) continue; vis[to]=true; if(!match[to]||dfs(match[to])) { match[id]=to; match[to]=id; return true; } } return false; } int hungary() { int ret=0; memset(match,0,sizeof(match)); for(int i=1;i<=n;i++) for(int j=1+((i-1)&1);j<=m;j+=2) { memset(vis,0,sizeof(vis)); ret+=dfs(pos(i,j)); } return ret; } int ans[N]; int main() { int sx,sy,step; cin>>n>>m; for(int i=1;i<=n;i++) { for(int j=1;j<=m;j++) { scanf(" %c",&map[i][j]); if(map[i][j]=='.') sx=i,sy=j,map[i][j]=3; else if(map[i][j]=='X') map[i][j]=3; else map[i][j]=2; } } for(int i=1;i<=n;i++) for(int j=1;j<=m;j++) build(i,j);//建边 hungary(); cin>>step; for(int i=1;i<=step*2;i++) { memset(vis,0,sizeof(vis)); disable[pos(sx,sy)]=true; //win[i]:在当前状态下是否有必胜策略 win[i]=(match[pos(sx,sy)])&&(!dfs(match[pos(sx,sy)]));//如果空格不必须在最大匹配中 if(win[i]) match[match[pos(sx,sy)]]=0;//释放匹配的点 scanf("%d%d",&sx,&sy); } int cnt=0; for(int i=1;i<step*2;i+=2) { if(win[i]&&win[i+1]) ans[++cnt]=(i+1)/2; } cout<<cnt<<endl; for(int i=1;i<=cnt;i++) printf("%d\n", ans[i]); }
看都看了,顺手点个推荐呗 :)