[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]);
}
看都看了,顺手点个推荐呗 :)

浙公网安备 33010602011771号