zoukankan      html  css  js  c++  java
  • 【BZOJ2437】【NOI2011】兔兔与蛋蛋(博弈论,二分图匹配)

    【BZOJ2437】【NOI2011】兔兔与蛋蛋(博弈论,二分图匹配)

    题面

    BZOJ

    题解

    考虑一下暴力吧。
    对于每个状态,无非就是要考虑它是否是必胜状态
    这个直接用(dfs)爆搜即可。
    这样子对于每一次操作,考虑兔兔操作后的状态是否是必胜状态
    如果这个状态是必胜状态,并且蛋蛋操作完后的状态是(兔兔的)必败状态
    那么这就是一个“犯错误”的操作。
    这样暴力可以拿到(75pts)

    #include<iostream>
    #include<cstdio>
    #include<cstdlib>
    #include<cstring>
    #include<cmath>
    #include<algorithm>
    #include<set>
    #include<map>
    #include<vector>
    #include<queue>
    using namespace std;
    #define ll long long
    #define RG register
    #define MAX 45
    inline int read()
    {
        RG int x=0,t=1;RG char ch=getchar();
        while((ch<'0'||ch>'9')&&ch!='-')ch=getchar();
        if(ch=='-')t=-1,ch=getchar();
        while(ch<='9'&&ch>='0')x=x*10+ch-48,ch=getchar();
        return x*t;
    }
    int n,m,X,Y;
    char ch[MAX];
    int g[MAX][MAX],zt[MAX];
    int d[4][2]={1,0,-1,0,0,1,0,-1};
    int ans[MAX*MAX],top,Q;
    bool dfs(int x,int y,int z)
    {
    	for(int i=0;i<4;++i)
    	{
    		int xx=x+d[i][0],yy=y+d[i][1];
    		if(xx<1||xx>n||yy<1||yy>m||g[xx][yy]!=z)continue;
    		swap(g[x][y],g[xx][yy]);
    		if(!dfs(xx,yy,z^1)){swap(g[x][y],g[xx][yy]);return true;}
    		swap(g[x][y],g[xx][yy]);
    	}
    	return false;
    }
    int main()
    {
    	n=read();m=read();
    	for(int i=1;i<=n;++i)
    	{
    		scanf("%s",ch+1);
    		for(int j=1;j<=m;++j)
    			if(ch[j]=='X')g[i][j]=1;
    			else if(ch[j]=='O')g[i][j]=0;
    			else if(ch[j]=='.')g[i][j]=2;
    	}
    	for(int i=1;i<=n;++i)
    		for(int j=1;j<=m;++j)
    			if(g[i][j]==2){X=i;Y=j;break;}
    	Q=read();
    	for(int i=1,x,y;i<=Q;++i)
    	{
    		x=read(),y=read();
    		zt[i]=dfs(X,Y,0);
    		swap(g[x][y],g[X][Y]);
    		X=x;Y=y;
    		if(zt[i]&&dfs(X,Y,1))ans[++top]=i;
    		x=read();y=read();
    		swap(g[x][y],g[X][Y]);
    		X=x;Y=y;
    	}
    	printf("%d
    ",top);
    	for(int i=1;i<=top;++i)printf("%d
    ",ans[i]);
    	return 0;
    }
    
    

    观察一下基本的事实。
    考虑走的方案是否可能出现一个环。
    无论环有多大,似乎都是一样的,所以我们就考虑在(2 imes 2)的方格中移动
    初始时空格在((1,1)),它和((1,2))交换位置,此时,((1,1))为白
    然后((1,2))((2,2))交换位置,((1,2))为黑
    ((2,2))((2,1))交换位置,((2,2))为白
    此时如果((2,1))能与((1,1))交换位置,那么((1,1))需要是黑色
    但是((1,1))是白色,所以显然不能成环。
    对于一个更大的环,无非是长(+1)或者宽(+1)拓展出来的,每次多走两步,对于黑白没有影响。

    既然不能成环,意味着每个点只会被经过一次。
    那么,我们可以重新开一下这个过程,可以理解为从空格开始,
    走一条路径,路径上黑白相间。
    黑白相间?有点像二分图的感觉。每条增广路不就是黑白相间吗?
    因为先手的是白格子,所以可以把空格开成黑格子
    这样子就是要从这个黑格子这里找一条增广路出去。
    再考虑一下胜利的情况,如果先手胜利,那么从黑格子连向了一个白格子
    然后找不到增广路了,此时白格子胜。
    继续把这个情况向上拓展,我们可以得到。

    如果当前点一定在二分图的最大匹配中,那么先手必胜。因为先手始终可以沿着最大匹配的匹配边走,而最大匹配中交错路的数量为奇数条,也就是进行奇数次操作,意味着后手最后无法操作,此时先手必胜。

    那么,每次进行判定当前点是否在二分图的最大匹配中,是否一定被选中即可判定先手是否必胜,依次可以计算答案。

    至于如何计算当前点是否一定在二分图的最大匹配中?
    把当前点给(ban)掉,在增广的时候强行不选,然后对其匹配点进行增广,
    如果能够找到新的增广路,意为这当前点可以被替代,
    否则当前点一定在最大匹配中。
    这题好神仙啊

    #include<iostream>
    #include<cstdio>
    #include<cstdlib>
    #include<cstring>
    #include<cmath>
    #include<algorithm>
    #include<set>
    #include<map>
    #include<vector>
    #include<queue>
    using namespace std;
    #define ll long long
    #define RG register
    #define MAX 45
    inline int read()
    {
        RG int x=0,t=1;RG char ch=getchar();
        while((ch<'0'||ch>'9')&&ch!='-')ch=getchar();
        if(ch=='-')t=-1,ch=getchar();
        while(ch<='9'&&ch>='0')x=x*10+ch-48,ch=getchar();
        return x*t;
    }
    int n,m,X,Y;
    char ch[MAX];
    int g[MAX][MAX],zt[MAX*MAX];
    int d[4][2]={1,0,-1,0,0,1,0,-1};
    int ans[MAX*MAX],top,Q;
    int bh[MAX][MAX],tot;
    struct Line{int v,next;}e[MAX*MAX<<3];
    int h[MAX*MAX],cnt=1;
    inline void Add(int u,int v){e[cnt]=(Line){v,h[u]};h[u]=cnt++;}
    int match[MAX*MAX],tim,vis[MAX*MAX];
    bool ban[MAX*MAX];
    bool dfs(int u)
    {
    	if(ban[u])return false;
    	for(int i=h[u];i;i=e[i].next)
    		if(vis[e[i].v]!=tim&&!ban[e[i].v])
    		{
    			vis[e[i].v]=tim;
    			if(!match[e[i].v]||dfs(match[e[i].v]))
    			{
    				match[e[i].v]=u;match[u]=e[i].v;
    				return true;
    			}
    		}
    	return false;
    }
    int main()
    {
    	n=read();m=read();
    	for(int i=1;i<=n;++i)
    	{
    		scanf("%s",ch+1);
    		for(int j=1;j<=m;++j)
    			if(ch[j]=='X')g[i][j]=1;
    			else if(ch[j]=='O')g[i][j]=0;
    			else if(ch[j]=='.')g[i][j]=1,X=i,Y=j;
    	}
    	for(int i=1;i<=n;++i)
    		for(int j=1;j<=m;++j)
    			bh[i][j]=++tot;
    	for(int i=1;i<=n;++i)
    		for(int j=1;j<=m;++j)
    			if(g[i][j])
    				for(int k=0;k<4;++k)
    				{
    					int x=i+d[k][0],y=j+d[k][1];
    					if(x<1||x>n||y<1||y>m||g[x][y])continue;
    					Add(bh[i][j],bh[x][y]);
    					Add(bh[x][y],bh[i][j]);
    				}
    	for(int i=1;i<=n;++i)
    		for(int j=1;j<=m;++j)
    			if(g[i][j])++tim,dfs(bh[i][j]);
    	Q=read();
    	for(int i=1,id;i<=Q+Q;++i)
    	{
    		id=bh[X][Y];ban[id]=true;
    		if(match[id])
    		{
    			int nw=match[id];match[nw]=match[id]=0;
    			++tim;zt[i]=!dfs(nw);
    		}
    		X=read();Y=read();
    	}
    	for(int i=1;i<=Q;++i)
    		if(zt[i+i-1]&zt[i+i])ans[++top]=i;
    	printf("%d
    ",top);
    	for(int i=1;i<=top;++i)printf("%d
    ",ans[i]);
    	return 0;
    }
    
    
  • 相关阅读:
    java Random 随机重排
    java Random 带权重的随机选择
    java 枚举
    springboot docker 部署
    docker 国内镜像加速
    BigDecimal使用
    java继承实现的基本原理
    MySQL 数据类型
    Knockout: 让ViewModel从htm中剥离出去。
    常用代码之五:RequireJS, 一个Define需要且只能有一个返回值/对象,一个JS文件里只能放一个Define.
  • 原文地址:https://www.cnblogs.com/cjyyb/p/9191628.html
Copyright © 2011-2022 走看看