总结一下博弈+二分图匹配的类型?
一开始是这道纯博弈水题:bzoj2463[中山市选2009]谁能赢呢?
万万没想到没有gdoi的题目的b站居然有大山市选题orz.
题目大意:
给定一个n×n的棋盘,一个石头被放在棋盘的左上角。他们轮流移动石头。每一回合,选手只能把石头向上,下,左,右四个方向移动一格,并且要求移动到的格子之前不能被访问过。谁不能移动石头了就算输。假如小明先移动石头,而且两个选手都以最优策略走步,问最后谁能赢?
解法的话我直接说正确证明好了:
网上的证明就是用1*2的骨牌拼..如果矩阵有偶数个点,那么一定可以用1*2的骨牌拼满。起点就相当于占了骨牌的一端,那么先手走的就是骨牌的另一端。后手再走的话必定是骨牌的前端,所以先手无论怎么样一定有个骨牌的另一端可以走,即先手必胜。反之,奇数个点的话,去掉起点(可以证明剩下的图形也是可以被拼满的),那么就变成了先手面临着必败的局面。
[然后在我看证明的时候被d了qwq???(:一行代码也看题解?!)
因为这个图形是矩阵,而在矩阵上拼骨牌直接判奇偶就可以了(dalao说显然= =)。但是我们不禁思考,如果这个图形不是矩阵呢?这个时候我们想到了二分图匹配。把格子黑白染色后不就可以看成骨牌的前端后端吗。于是就可以根据这个来解决一系列(?)的与博弈有关的二分图匹配问题了。
丢一道题:bzoj1443[JSOI2009]游戏Game
题目大意:
给定一个n*m的矩阵,上面有些坏掉的格子。两个人玩游戏,只能在好的格子上面进行。先手一开始选择一个格子作为起点放入一个棋子,由后手开始移动。两个人轮流将棋子移动到相邻的格子中,每个格子只能走一次。问先手能不能赢,若可以则输出一开始能作为起点的格子的坐标。
//为什么我会觉得这道题比下面noi那道还难想QwQ
题解的话:
把格子黑白染色后构成一个二分图跑最大匹配。可以发现,如果该二分图有完美匹配的话,先手一定输,因为后手一定有路可走(参见bzoj2463的骨牌证明。
而在没有完美匹配的情况下:
1、若先手选择了一个非匹配点,那么后手到的一定是个匹配点(否则与最大匹配矛盾)。于是先手就一定可以顺着匹配边走下去。所以这样的话先手必胜。
2、若先手选择了一个匹配点,后手就保证一定能沿着匹配边走。先手必输。
所以赢的方案其实就是那些最大匹配中的非必需点。而非必需点就是通过非匹配点能走到的跟这个非匹配点同一边的那些点(如果不懂思考一下就能懂了),用个dfs搜搜就好了。
再丢一道虽然我觉得比上面那道好想但我还是%了题解的:bzoj2437[Noi2011]兔兔与蛋蛋
题目大意:
n*m的棋盘,除去一个空的格子之外其他格子都是要么黑色要么白色。兔子先手,每次将一个与空格相邻的白格移进空格。蛋蛋只能移黑格。第一个不能按规则移动的人算输。现在给出棋盘和他们的操作。问兔兔的那些操作是错误的。
当且仅当,操作前是必胜态,而操作后对手是必胜态时认为该操作是错误的。
题解:
这道题的操作就相当于将空格按照黑白黑白这样的顺序移动。
所以就把格子黑白染色,因为先手走白格所以让起点为黑色。但是可以发现,距离空格为奇数的黑格子和距离空格为偶数的白格子是一定走不到的。所以就先把这些格子去掉。然后二分图构图。
如果起点在一定最大匹配中,则一定先手必胜。因为起点一定在奇数边的交错轨中,只要每次都沿着匹配的边走,一定可以赢。
如果起点不是最大匹配的必需点,那么后手必胜。因为第一步走到的点,一定在不包括起点的最大匹配中,那么后手就可以沿着走匹配边获胜。
所以只要当前点是最大匹配中的必需点,那么此刻就是必胜态。然后根据这个来判断兔子有没有走错就好了
判断一个点是不是必需点的话,只要看看他的匹配能不能找到新的母牛就好了。
这些类型的题的胜负判定大概都是谁不能走谁输,而且有一个格子不能走两次这样的设定或隐藏条件。然后从这两题里面,我觉得最大匹配里的非必需点出现频率hin高啊。而一条匹配边其实就可以看成一个1*2的骨牌,先走到一个匹配点的人必败。像非必需点(就是不一定在最大匹配里的点包括一定不在最大匹配里的点),由它出发到所到达的点一定在最大匹配中(否则与最大匹配矛盾),所以非必需点的判断往往是解决这类题的关键(雾)。
做这类题,让我感觉重新学了一遍二分图。。。以前根本没有匹配非匹配啊必需非必需的概念orz
//必需还是必须其实我根本不知道用那个好orz
一开始是这道纯博弈水题:bzoj2463[中山市选2009]谁能赢呢?
万万没想到没有gdoi的题目的b站居然有大山市选题orz.
题目大意:
给定一个n×n的棋盘,一个石头被放在棋盘的左上角。他们轮流移动石头。每一回合,选手只能把石头向上,下,左,右四个方向移动一格,并且要求移动到的格子之前不能被访问过。谁不能移动石头了就算输。假如小明先移动石头,而且两个选手都以最优策略走步,问最后谁能赢?
解法的话我直接说正确证明好了:
网上的证明就是用1*2的骨牌拼..如果矩阵有偶数个点,那么一定可以用1*2的骨牌拼满。起点就相当于占了骨牌的一端,那么先手走的就是骨牌的另一端。后手再走的话必定是骨牌的前端,所以先手无论怎么样一定有个骨牌的另一端可以走,即先手必胜。反之,奇数个点的话,去掉起点(可以证明剩下的图形也是可以被拼满的),那么就变成了先手面临着必败的局面。
[然后在我看证明的时候被d了qwq???(:一行代码也看题解?!)
因为这个图形是矩阵,而在矩阵上拼骨牌直接判奇偶就可以了(dalao说显然= =)。但是我们不禁思考,如果这个图形不是矩阵呢?这个时候我们想到了二分图匹配。把格子黑白染色后不就可以看成骨牌的前端后端吗。于是就可以根据这个来解决一系列(?)的与博弈有关的二分图匹配问题了。
丢一道题:bzoj1443[JSOI2009]游戏Game
题目大意:
给定一个n*m的矩阵,上面有些坏掉的格子。两个人玩游戏,只能在好的格子上面进行。先手一开始选择一个格子作为起点放入一个棋子,由后手开始移动。两个人轮流将棋子移动到相邻的格子中,每个格子只能走一次。问先手能不能赢,若可以则输出一开始能作为起点的格子的坐标。
//为什么我会觉得这道题比下面noi那道还难想QwQ
题解的话:
把格子黑白染色后构成一个二分图跑最大匹配。可以发现,如果该二分图有完美匹配的话,先手一定输,因为后手一定有路可走(参见bzoj2463的骨牌证明。
而在没有完美匹配的情况下:
1、若先手选择了一个非匹配点,那么后手到的一定是个匹配点(否则与最大匹配矛盾)。于是先手就一定可以顺着匹配边走下去。所以这样的话先手必胜。
2、若先手选择了一个匹配点,后手就保证一定能沿着匹配边走。先手必输。
所以赢的方案其实就是那些最大匹配中的非必需点。而非必需点就是通过非匹配点能走到的跟这个非匹配点同一边的那些点(如果不懂思考一下就能懂了),用个dfs搜搜就好了。
再丢一道虽然我觉得比上面那道好想但我还是%了题解的:bzoj2437[Noi2011]兔兔与蛋蛋
题目大意:
n*m的棋盘,除去一个空的格子之外其他格子都是要么黑色要么白色。兔子先手,每次将一个与空格相邻的白格移进空格。蛋蛋只能移黑格。第一个不能按规则移动的人算输。现在给出棋盘和他们的操作。问兔兔的那些操作是错误的。
当且仅当,操作前是必胜态,而操作后对手是必胜态时认为该操作是错误的。
题解:
这道题的操作就相当于将空格按照黑白黑白这样的顺序移动。
所以就把格子黑白染色,因为先手走白格所以让起点为黑色。但是可以发现,距离空格为奇数的黑格子和距离空格为偶数的白格子是一定走不到的。所以就先把这些格子去掉。然后二分图构图。
如果起点在一定最大匹配中,则一定先手必胜。因为起点一定在奇数边的交错轨中,只要每次都沿着匹配的边走,一定可以赢。
如果起点不是最大匹配的必需点,那么后手必胜。因为第一步走到的点,一定在不包括起点的最大匹配中,那么后手就可以沿着走匹配边获胜。
所以只要当前点是最大匹配中的必需点,那么此刻就是必胜态。然后根据这个来判断兔子有没有走错就好了
判断一个点是不是必需点的话,只要看看他的匹配能不能找到新的母牛就好了。
这些类型的题的胜负判定大概都是谁不能走谁输,而且有一个格子不能走两次这样的设定或隐藏条件。然后从这两题里面,我觉得最大匹配里的非必需点出现频率hin高啊。而一条匹配边其实就可以看成一个1*2的骨牌,先走到一个匹配点的人必败。像非必需点(就是不一定在最大匹配里的点包括一定不在最大匹配里的点),由它出发到所到达的点一定在最大匹配中(否则与最大匹配矛盾),所以非必需点的判断往往是解决这类题的关键(雾)。
做这类题,让我感觉重新学了一遍二分图。。。以前根本没有匹配非匹配啊必需非必需的概念orz
//必需还是必须其实我根本不知道用那个好orz
下面是三题的代码:
/**************************************************************
Problem: 2463
User: //不告诉你
Language: C++
Result: Accepted
Time:20 ms
Memory:1288 kb
****************************************************************/
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
int main()
{
int n;
while (1)
{
scanf("%d",&n);
if (n==0) break;
if ((n*n)&1) printf("Bob\n");
else printf("Alice\n");
}
return 0;
}
/**************************************************************
Problem: 1443
User: //不告诉你
Language: C++
Result: Accepted
Time:264 ms
Memory:1960 kb
****************************************************************/
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
#define maxn 10100
struct node
{
int x,y,next;
}a[maxn*4];int len,first[maxn];
int bf[maxn],ask[maxn],tim;
int map[110][110];bool as[maxn];
void ins(int x,int y)
{
len++;a[len].x=x;a[len].y=y;
a[len].next=first[x];first[x]=len;
}
bool ffind(int x)
{
for (int i=first[x];i!=-1;i=a[i].next)
if (ask[a[i].y]!=tim)
{
int y=a[i].y;
ask[y]=tim;
if (bf[y]==-1 || ffind(bf[y]))
{
bf[y]=x;bf[x]=y;
return true;
}
}
return false;
}
void dfs(int x)
{
as[x]=true;
for (int i=first[x];i!=-1;i=a[i].next)
if (!as[bf[a[i].y]])
dfs(bf[a[i].y]);
}
int main()
{
int n,m,num,ans,i,j;char c;
scanf("%d%d",&n,&m);
num=len=0;memset(first,-1,sizeof(first));
for (i=1;i<=n;i++)
{
scanf("\n");
for (j=1;j<=m;j++)
{
scanf("%c",&c);
if (c=='#') map[i][j]=0;
else map[i][j]=++num;
}
}
for (i=1;i<=n;i++)
for (j=1;j<=m;j++)
if (map[i][j])
{
if (i<n && map[i+1][j]) {ins(map[i][j],map[i+1][j]);ins(map[i+1][j],map[i][j]);}
if (j<m && map[i][j+1]) {ins(map[i][j],map[i][j+1]);ins(map[i][j+1],map[i][j]);}
}
tim=ans=0;
memset(bf,-1,sizeof(bf));
memset(ask,0,sizeof(ask));
for (i=1;i<=num;i++)
{
if (bf[i]==-1)
{
tim++;
if (ffind(i)) ans++;
}
as[i]=false;
}
if (ans*2==num) printf("LOSE\n");
else
{
for (i=1;i<=num;i++)
if (bf[i]==-1)
{
dfs(i);
}
printf("WIN\n");
for (i=1;i<=n;i++)
for (j=1;j<=m;j++) if (map[i][j])
{
if (as[map[i][j]]) printf("%d %d\n",i,j);
}
}
return 0;
}
/**************************************************************
Problem: 2437
User: //不告诉你
Language: C++
Result: Accepted
Time:20 ms
Memory:1440 kb
****************************************************************/
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
#define maxn 2100
struct node
{
int x,y,next;
}a[maxn*4];int len,first[maxn];
int num[50][50],sum;
bool del[maxn],map[50][50],ret[maxn];
int ans,as[maxn],bf[maxn],ask[maxn],tim;
void ins(int x,int y)
{
++len;a[len].x=x;a[len].y=y;
a[len].next=first[x];first[x]=len;
}
bool ffind(int x)
{
if (del[x]) return false;
for (int i=first[x];i!=-1;i=a[i].next)
if (ask[a[i].y]!=tim)
{
int y=a[i].y;
ask[y]=tim;
if (del[y]) continue;
if (bf[y]==-1 || ffind(bf[y]))
{
bf[y]=x; bf[x]=y;
return true;
}
}
return false;
}
int main()
{
int n,m,i,j,x,y,q;char c;
scanf("%d%d",&n,&m);
for (i=1;i<=n;i++)
{
scanf("\n");
for (j=1;j<=m;j++)
{
scanf("%c",&c);
if (c=='X') map[i][j]=1;
else if (c=='O') map[i][j]=0;
else if (c=='.') {x=i,y=j;map[i][j]=1;}
}
}
sum=len=0;
memset(del,0,sizeof(del));
memset(first,-1,sizeof(first));
memset(num,0,sizeof(num));
for (i=1;i<=n;i++)
for (j=1;j<=m;j++)
if ((map[i][j] && ((abs(x-i)+abs(y-j))&1)==0) || (!map[i][j] && (abs(x-i)+abs(y-j))&1)) num[i][j]=++sum;
for (i=1;i<=n;i++)
for (j=1;j<=m;j++)
if (num[i][j])
{
if (i<n && num[i+1][j]) ins(num[i][j],num[i+1][j]);
if (i>1 && num[i-1][j]) ins(num[i][j],num[i-1][j]);
if (j<m && num[i][j+1]) ins(num[i][j],num[i][j+1]);
if (j>1 && num[i][j-1]) ins(num[i][j],num[i][j-1]);
}
tim=0;memset(bf,-1,sizeof(bf));
memset(ask,0,sizeof(ask));
for (i=1;i<=sum;i++)
if (bf[i]==-1)
{
tim++;ffind(i);
}
scanf("%d",&q);q<<=1;
ans=0;memset(as,0,sizeof(as));
for (i=1;i<=q;i++)
{
del[num[x][y]]=true;
if (bf[num[x][y]]!=-1)
{
int tt=bf[num[x][y]];
bf[tt]=bf[num[x][y]]=-1;
tim++;
ret[i]=!ffind(tt);
//如果成功增广,就说明那个点不一定在最大匹配上,就是必败态
}else
{
ret[i]=false;//操作前就是必败了
}
scanf("%d%d",&x,&y);
}
for (i=1;i<=q;i+=2)
if (ret[i] && ret[i+1]) as[++ans]=(i+1)/2;
printf("%d\n",ans);
for (i=1;i<=ans;i++)
printf("%d\n",as[i]);
return 0;
}