LOJ6033「雅礼集训 2017 Day2」棋盘游戏(二分图最大匹配必经点)
考虑把棋盘黑板染色,然后给这个二分图跑最大匹配
某位置开始后手必胜,当且仅当存在一个最大匹配使得这个点不是匹配点
证明:若存在,则先手每次只需要走到上个点对应的匹配点,最终一定是后手无路可走;若不存在,则起始点旁边必定都是匹配点,先手走上去转化成存在的情况
所以问题变成了求二分图最大匹配必经点
考虑左部点,对于每个非匹配边,从左到右连有向边,对于匹配边,从右到左连有向边,那么从每个非匹配左部点开始遍历,遍历到的所有左部点都不是必经点(因为走出了一条交替路,所有匹配边变非匹配边,非匹配边变匹配边,那匹配点也全边非匹配点了)
对于右部点同理
令:二分图最大匹配可行边、必须边
最大匹配是完备匹配
把二分图中的非匹配边看作从左部向右部的有向边,匹配边看作从右部向左部的有向边,构成一张新的有向图。
- 必须边 \((x,y)\):\((x,y)\) 是当前二分图的匹配边,且 \(x,y\) 在新的有向图中属于不同的强连通分量。
- 可行边 \((x,y)\):\((x,y)\) 是当前二分图的匹配边,或 \(x,y\) 在新的有向图中属于同一个强连通分量。
最大匹配不一定是完备匹配
- 必须边 \((x,y)\):\((x,y)\) 的流量为 \(1\),且在残量网络上属于不同的强连通分量。
- 可行边 \((x,y)\):\((x,y)\) 的流量为 \(1\),或在残量网络上属于同一个强连通分量。
#define N 20006
#define M 2000006
struct Graph{
int fir[N],nex[M],to[M],tot;
inline void add(int u,int v,int flag=1){
to[++tot]=v;
nex[tot]=fir[u];fir[u]=tot;
if(flag) add(v,u,0);
}
inline void clear(){tot=0;std::memset(fir,0,sizeof fir);}
}G,T;
int match[N],vis[N];
int dfs(int u){
for(int v,i=G.fir[u];i;i=G.nex[i]){
v=G.to[i];
if(vis[v]) continue;
vis[v]=1;
if(!match[v]||dfs(match[v])) return match[v]=u,match[u]=v,1;
}
return 0;
}
int n,m;
char s[106][106];int id[106][106];
int isLeft[N];
const int di[]={0,1,0,-1};
const int dj[]={1,0,-1,0};
inline void rebuild(int op){
for(int i=1;i<=n;i++)for(int j=1;j<=m;j++)if(s[i][j]=='.'&&((i+j)&1)){
for(int i_,j_,k=0;k<4;k++){
i_=i+di[k];j_=j+dj[k];
if(i_<1||j_<1||i_>n||j_>m||s[i_][j_]=='#') continue;
if((match[id[i][j]]==id[i_][j_])^op) T.add(id[i][j],id[i_][j_],0);
else T.add(id[i_][j_],id[i][j],0);
}
}
}
inline void build(){
for(int i=1;i<=n;i++)for(int j=1;j<=m;j++)if(s[i][j]=='.'){
for(int i_,j_,k=0;k<2;k++){
i_=i+di[k];j_=j+dj[k];
if(i_<1||j_<1||i_>n||j_>m||s[i_][j_]=='#') continue;
G.add(id[i][j],id[i_][j_]);
}
}
}
int yes[N];
void dfs2(int u,int k){
if(vis[u]) return;
vis[u]=1;yes[u]=(isLeft[u]^k);
for(int i=T.fir[u];i;i=T.nex[i]) dfs2(T.to[i],k);
}
inline void work(){
rebuild(1);
std::memset(vis,0,sizeof vis);
for(int i=1;i<=n;i++)for(int j=1;j<=m;j++)if(!match[id[i][j]]&&s[i][j]=='.'&&((i+j)&1)) dfs2(id[i][j],0);
T.clear();
rebuild(0);
for(int i=1;i<=n;i++)for(int j=1;j<=m;j++)if(!match[id[i][j]]&&s[i][j]=='.'&&!((i+j)&1)) dfs2(id[i][j],1);
}
int main(){
n=read();m=read();
for(int i=1;i<=n;i++){
scanf("%s",s[i]+1);
for(int j=1;j<=m;j++)if(s[i][j]=='.') id[i][j]=++id[0][0],isLeft[id[0][0]]=((i+j)&1);
}
build();
for(int i=1;i<=n;i++)for(int j=1;j<=m;j++)if(((i+j)&1)&&s[i][j]=='.'){
std::memset(vis,0,sizeof vis);
dfs(id[i][j]);
}
work();
int ans=0;
for(int i=1;i<=n;i++)for(int j=1;j<=m;j++)if(s[i][j]=='.'&&(!match[id[i][j]]||yes[id[i][j]])) ans++;
printf("%d\n",ans);
for(int i=1;i<=n;i++)for(int j=1;j<=m;j++)if(s[i][j]=='.'&&(!match[id[i][j]]||yes[id[i][j]])) printf("%d %d\n",i,j);
return 0;
}