bzoj 1443: [JSOI2009]游戏Game (二分图博弈+网络流)

来源:互联网 发布:钢铁雄心4卡顿优化补丁 编辑:程序博客网 时间:2024/05/20 20:03

题目描述

传送门

题目大意:给出一个n*m的棋盘,有一些障碍点,先手选择一个点,然后后手可以移动到上下左右四个格子(不能是障碍点)。两人轮流移动,不能移动到已经到达过的位置,最后不能操作的人输。

题解

二分图博弈
特点:(1)博弈双方轮流进行操作
(2)博弈状态可以分成两类,分别对于匹配的X,Y集。任意合法决策使博弈状态改变
(3)任意状态不能重复到达
(4)不能操作的人输

对于这道来说,从一个点只能转移到他周围的四个点,那么我们对棋盘进行黑白染色,那么从黑点只能到达白点,从白点只能到达黑点。黑白点就可以看成不同的博弈状态。
那么如果把相邻的点用边连接,就构成了一个二分图。
对于先手必胜的点:一定是求完最大匹配后非匹配的点。
为什么?如果放到非匹配的点上,那么后手只能到达匹配点上(否则两个点能匹配,就不满足最大匹配了)
后手到达匹配点,那么先手就能走匹配边。
然后后手只能走一条不是匹配边的边,但是他到达的点一定有一条匹配边,那么先手就可以继续走。
所以最后不能操作的一定是后手。

但是对于一个二分图,可能的最大匹配不止一种,但是任意一个最大匹配中的的非匹配点都是合法的答案。
那么如何高效的统计呢?
1.从S出发,走容量不为0的边所能到达的属于X集合的点。能到达点可以分成两类:一是非匹配点(直接到达),二是经过一个非匹配点,然后走一条类似增广路所到达的匹配点(即可以将这条路上的匹配边和非匹配边翻转得到另一组解,而这条路的终点就是另一组解中的非匹配点)
2.从T出发,沿反向弧为0的边走所能到达的属于Y集合的点。能到达的点也可以分成两类,与上面S的分析方法类似。

那么二分图博弈就转换成了最大匹配问题。

代码

#include<iostream>#include<cstdio>#include<algorithm>#include<cstring>#include<cmath>#include<queue>#define N 103#define M 1000003#define inf 1000000000using namespace std;int point[M],nxt[M],v[M],remain[M],n,m,c[N][N],last[M],cur[M],vis[M],opt[M],px[M],py[M];int dx[10]={0,1,0,-1},dy[10]={1,0,-1,0},tot,cnt,top,st[M],deep[M],num[M],S,T;char ch[N][N];struct data{    int x,y;}a[M];void add(int x,int y,int z){    tot++; nxt[tot]=point[x]; point[x]=tot; v[tot]=y; remain[tot]=z;    tot++; nxt[tot]=point[y]; point[y]=tot; v[tot]=x; remain[tot]=0;}int addflow(int s,int t){    int now=t; int ans=inf;    while (now!=s) {        ans=min(ans,remain[last[now]]);        now=v[last[now]^1];    }    now=t;    while (now!=s) {        remain[last[now]]-=ans;        remain[last[now]^1]+=ans;        now=v[last[now]^1];    }    return ans;}void bfs(int s,int t){    for (int i=1;i<=t;i++) deep[i]=t+1;    queue<int> p; p.push(t);    deep[t]=0;    while (!p.empty()) {        int now=p.front(); p.pop();        for (int i=point[now];i!=-1;i=nxt[i])         if (deep[v[i]]==t+1&&remain[i^1])          deep[v[i]]=deep[now]+1,p.push(v[i]);    }}int isap(int s,int t){    bfs(s,t); int now=s; int ans=0;    for (int i=1;i<=t;i++) num[deep[i]]++;    for (int i=1;i<=t;i++) cur[i]=point[i];    while (deep[s]<t) {        if (now==t) {            ans+=addflow(s,t);            now=s;        }        bool mark=false;        for (int i=point[now];i!=-1;i=nxt[i])         if (deep[v[i]]+1==deep[now]&&remain[i]){            mark=true;            cur[now]=i;            last[v[i]]=i;            now=v[i];            break;         }        if (!mark){            int minn=t+1;            for (int i=point[now];i!=-1;i=nxt[i])             if (remain[i]) minn=min(minn,deep[v[i]]);            if (!--num[deep[now]]) break;            num[deep[now]=minn+1]++;            if (now!=s) now=v[last[now]^1];         }    }    return ans;}void solve(int x,int t){    vis[x]=1;    if (opt[x]==t) st[++top]=x;    for (int i=point[x];i!=-1;i=nxt[i])     if (!vis[v[i]]&&(remain[i]==(t!=2)))       solve(v[i],t);}int cmp(data a,data b){    return a.x<b.x||a.x==b.x&&a.y<b.y;}int main(){    freopen("a.in","r",stdin);    scanf("%d%d",&n,&m);    for (int i=1;i<=n;i++) scanf("%s",ch[i]+1);    for (int i=1;i<=n;i++)     for (int j=1;j<=m;j++)      if (ch[i][j]=='.') {        c[i][j]=++cnt;        px[cnt]=i; py[cnt]=j;      }    S=cnt+1; T=S+1; tot=-1;    memset(point,-1,sizeof(point));    for (int i=1;i<=n;i++)     for (int j=1;j<=m;j++)      if (ch[i][j]=='.') {        if ((i^j)&1) {            add(S,c[i][j],1);            opt[c[i][j]]=1;            for (int k=0;k<4;k++) {              int tx=i+dx[k]; int ty=j+dy[k];              if (tx<1||ty<1||tx>n||ty>m||ch[tx][ty]=='#') continue;              add(c[i][j],c[tx][ty],1);             }        }else add(c[i][j],T,1),opt[c[i][j]]=2;      }    int flow=isap(S,T);    if ((flow<<1)==cnt) {        printf("LOSE\n");        return 0;    }     printf("WIN\n");    solve(S,1);    memset(vis,0,sizeof(vis));    solve(T,2);    for (int i=1;i<=top;i++) a[i].x=px[st[i]],a[i].y=py[st[i]];    sort(a+1,a+top+1,cmp);    for (int i=1;i<=top;i++) printf("%d %d\n",a[i].x,a[i].y);}
0 0