zoukankan      html  css  js  c++  java
  • [JSOI2009] 游戏

    一、题目

    点此看题

    二、解法

    真的神题,我至今不知道为什么要联想到最大匹配,说实话评个黑不过分吧

    一看就用不了 ( t sg) 函数,这启示我们要去找稳态。考虑行走的过程可以看成二分图上增广的过程,利用完美匹配后不存在增广路这一性质,我们把行走放在二分图上思考。

    对原图黑白染色之后把非障碍点连边建立二分图,先用匈牙利算法跑出最大匹配。

    结论:如果存在一个完美匹配使得某个点不在匹配点集中,那么这个点是必败态

    证明:我们就考虑这个特殊的完美匹配,先手一定走到一个匹配点,然后后手走到这个点对应的点,因为不能走回头路,所以下一次先手必然走到一个新的匹配点,如果走到一个未匹配点那么就产生了增广路,与完美匹配的条件矛盾。

    这样我们就找到了稳态,考虑路径长度一定是偶数,那么先手必败。

    称这样的点为非必选点,可见非必选点是必败态的充分条件。我们可以证明必选点(在所有完美匹配都出现)是必胜态的充分条件,就可以证明必选点是必败态的充要条件。

    证明:我们考虑必选点如果有边连向非必选点,那么就一步走到必败态,先手必胜。

    否则必选点一定只能走到必选点,那么我们把这个点删除,然后把对应的点变成非必选点,向得到的新二分图应用非必选点必败的结论,那么一步走到必败态。

    现在的问题变成了找到所有非必选点,首先匈牙利的未匹配点一定是非必选点。然后我们从这些点开始搜索,如果找到了一条非匹配边-匹配边-非匹配边-匹配边...的路径就说明路径上都是非匹配点,时间复杂度 (O(n^2m^2))

    三、总结

    不能走回头路的问题可以类比增广,找到最大匹配就能获得没有增广路的条件。

    #include <cstdio>
    #include <algorithm>
    using namespace std;
    const int M = 10005;
    int read()
    {
    	int x=0,f=1;char c;
    	while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
    	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
    	return x*f;
    }
    int n,m,k,sum,ans,tot,f[M],b[M],c[M],p[M],vis[M];
    int w,d[M],dx[4]={-1,1},dy[4]={0,0,1,-1};char s[M];
    struct edge
    {
    	int v,next;
    }e[10*M];
    int id(int x,int y)
    {
    	return (x-1)*m+y;
    }
    void add(int u,int v)
    {
    	e[++tot]=edge{u,f[v]},f[v]=tot;
    }
    int dfs(int u)
    {
    	for(int i=f[u];i;i=e[i].next)
    	{
    		int v=e[i].v;
    		if(vis[v]==k) continue;
    		vis[v]=k;
    		if(!p[v] || dfs(p[v]))
    		{
    			p[v]=u,p[u]=v;
    			return 1;
    		}
    	}
    	return 0;
    }
    void dfs2(int u)
    {
    	vis[u]=k;
    	for(int i=f[u];i;i=e[i].next)
    	{
    		int v=e[i].v;
    		if(p[v]==0 || p[v]==u || vis[p[v]]==k)
    			continue;
    		d[++w]=p[v];
    		dfs2(p[v]);
    	}
    }
    signed main()
    {
    	n=read();m=read();
    	for(int i=1;i<=n;i++)
    	{
    		scanf("%s",s+1);
    		for(int j=1;j<=m;j++)
    		{
    			b[id(i,j)]=s[j]=='#';
    			c[id(i,j)]=(i+j)&1;
    			sum+=(s[j]=='.');
    		}
    	}
    	//build the edge
    	for(int i=1;i<=n;i++)
    	for(int j=1;j<=m;j++) if(!b[id(i,j)])
    	for(int k=0;k<4;k++)
    	{
    		int x=i+dx[k],y=j+dy[k];
    		if(x<1 || y<1 || x>n || y>m || b[id(x,y)])
    			continue;
    		add(id(i,j),id(x,y));
    	}
    	int o=n*m;
    	for(int i=1;i<=o;i++) if(!b[i] && c[i])
    		k++,ans+=dfs(i);
    	if(sum%2==0 && sum/2==ans)
    	{
    		puts("LOSE");
    		return 0;
    	}
    	k++;
    	for(int i=1;i<=o;i++) if(!p[i] && !b[i])
    		d[++w]=i,dfs2(i);
    	puts("WIN");
    	sort(d+1,d+1+w);
    	for(int i=1;i<=w;i++)
    	{
    		int x=(d[i]-1)/m+1,y=(d[i]-1)%m+1;
    		printf("%d %d
    ",x,y);
    	}
    }
    
  • 相关阅读:
    10分钟学会在Ubuntu 18.04 LTS上安装NFS服务器和客户端
    脱发、秃头防不胜防?这里有一份给码农的减压指南 [转自机器之心]
    Ubuntu通过apt-get安装指定版本和查询软件源有多少个版本
    ubuntu tree 查看目录结构
    Clion快捷键
    斜杠与反斜杠的记法
    C++ 既有约定
    docker其他参考资料
    标准错误重定向、标准输入重定向
    第一本Docker书读书笔记
  • 原文地址:https://www.cnblogs.com/C202044zxy/p/15477325.html
Copyright © 2011-2022 走看看