[二分图博弈] BZOJ 1443 [JSOI2009]游戏Game & BZOJ 2437 [Noi2011]兔兔与蛋蛋

二分图博弈

考虑这样一类博弈问题

  • 博弈状态可分为两类 即状态空间可分为两个集合 对应二分图X集和Y集
  • 任意合法的决策使状态从一类跳转到另一类 可以用二分图描述
  • 不可以转移至已访问的状态
  • 无法转移者判负。

问题转化从二分图指定起点开始轮流沿着边移动 不可重复访问点 无法移动判负。

不妨设起点 sX 考虑该二分图的某个最大匹配

  • s 不属于某个最大匹配 则先手所转移到的点yY一定属于最大匹配 后手沿着最大匹配的边走即可 后手不可能无路可走 因为这样对应着找到一条增广链 这与最大匹配矛盾 后手必胜
  • 若任意最大匹配都包含 s 则先手沿着最大匹配的边走即可 因为s必属于最大匹配 所以不存在长度为偶数的链 先手必胜

所以

若起点 s <script type="math/tex" id="MathJax-Element-50">s</script>是二分图最大匹配必配点 那么先手必胜 否则后手必胜

1443

很裸的二分图博弈
我们用最大流求出任意最大匹配
再用找弱增广链(长为偶数的链)的方法求出最大匹配必配点即可

#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#define cl(x) memset(x,0,sizeof(x))
using namespace std;

inline char nc(){
  static char buf[100000],*p1=buf,*p2=buf;
  return p1==p2&&(p2=(p1=buf)+fread(buf,1,100000,stdin),p1==p2)?EOF:*p1++;
}
inline void read(int &x){
  char c=nc(),b=1;
  for (;!(c>='0' && c<='9');c=nc()) if (c=='-') b=-1;
  for (x=0;c>='0' && c<='9';x=x*10+c-'0',c=nc()); x*=b;
}
inline void read(char *s){
  char c=nc(); int len=0;
  for (;!(c=='.' || c=='#');c=nc());
  for (;c=='.' || c=='#';s[++len]=c,c=nc()); 
}

#define U G[p].u
#define V G[p].v
const int N=10005;
namespace DINIC{
  struct edge{
    int u,v,f,next;
  }G[N<<4];
  int head[N],inum=1;
  inline void add(int u,int v,int f,int p){
    G[p].u=u; G[p].v=v; G[p].f=f; G[p].next=head[u]; head[u]=p;
  }
  inline void link(int u,int v,int f){
    add(u,v,f,++inum); add(v,u,0,++inum);
  }
  int S,T;
  int Q[N],l,r;
  int dis[N];
  inline bool bfs(){
    for (int i=1;i<=T;i++) dis[i]=-1;
    l=r=-1; Q[++r]=S; dis[S]=0;
    while (l<r){
      int u=Q[++l];
      for (int p=head[u];p;p=G[p].next)
    if (G[p].f && dis[V]==-1){
      dis[V]=dis[u]+1; Q[++r]=V;
      if (V==T) return 1;
    }
    }
    return 0;            
  }
  int cur[N];
  inline int dfs(int u,int flow){
    if (u==T) return flow;
    int used=0;
    for (int p=cur[u];p;p=G[p].next){
      cur[u]=p;
      if (G[p].f && dis[V]==dis[u]+1){
    int d=dfs(V,min(flow-used,G[p].f));
    G[p].f-=d; G[p^1].f+=d;
    used+=d; if (used==flow) break;
      }
    }
    if (used==0) dis[u]=-1;
    return used;
  }
  inline void Dinic(){
    while (bfs())
      memcpy(cur,head,sizeof(head)),dfs(S,1<<30);
  }
}

const int dx[]={0,0,1,-1};
const int dy[]={1,-1,0,0};
int n,m;
#define P(x,y) (((x)-1)*m+y)
char Map[105][105];
int left[N],right[N];

inline void Build(){
  using namespace DINIC;
  int sx,sy; S=n*m+1; T=n*m+2;
  for (int i=1;i<=n;i++)
    for (int j=1;j<=m;j++)
      if ((i+j)&1){
    if (Map[i][j]=='#') continue;
    for (int k=0;k<4;k++){
      sx=i+dx[k],sy=j+dy[k];
      if (sx<1 || sy<1 || sx>n || sy>m || Map[sx][sy]=='#') continue;
      link(P(i,j),P(sx,sy),1);
    }
    link(S,P(i,j),1);
      }else
    link(P(i,j),T,1);
  Dinic();
  for (int p=2;p<=inum;p+=2)
    if (U!=S && V!=T && !G[p].f){
      left[V]=U,right[U]=V;
    }
}

struct edge{
  int u,v,next;
}G[N<<2];
int head[N],inum;
inline void add(int u,int v,int p){
  G[p].u=u; G[p].v=v; G[p].next=head[u]; head[u]=p;
}

int vst[N];
inline void dfs(int u){
  if (vst[u]) return;
  vst[u]=1;
  for (int p=head[u];p;p=G[p].next)
    dfs(V);
}

typedef pair<int,int> abcd;
int pnt;
abcd ans[N];

int main(){
  freopen("t.in","r",stdin);
  freopen("t.out","w",stdout);
  read(n); read(m);
  for (int i=1;i<=n;i++) read(Map[i]);
  Build();int sx,sy;
  for (int i=1;i<=n;i++)
    for (int j=1;j<=m;j++)
      if ((i+j)&1){
    if (Map[i][j]=='#') continue;
    for (int k=0;k<4;k++){
      sx=i+dx[k],sy=j+dy[k];
      if (sx<1 || sy<1 || sx>n || sy>m || Map[sx][sy]=='#') continue;
      if (right[P(i,j)]==P(sx,sy))
        add(P(sx,sy),P(i,j),++inum);
      else
        add(P(i,j),P(sx,sy),++inum);
    }
      }
  for (int i=1;i<=n;i++)
    for (int j=1;j<=m;j++)
      if (((i+j)&1) && !right[P(i,j)])
    dfs(P(i,j));
  for (int i=1;i<=n;i++)
    for (int j=1;j<=m;j++)
      if (((i+j)&1) && vst[P(i,j)] && Map[i][j]!='#')
    ans[++pnt]=abcd(i,j);
  cl(head); inum=0; cl(vst);
  for (int i=1;i<=n;i++)
    for (int j=1;j<=m;j++)
      if (~(i+j)&1){
    if (Map[i][j]=='#') continue;
    for (int k=0;k<4;k++){
      sx=i+dx[k],sy=j+dy[k];
      if (sx<1 || sy<1 || sx>n || sy>m || Map[sx][sy]=='#') continue;
      if (left[P(i,j)]==P(sx,sy))
        add(P(sx,sy),P(i,j),++inum);
      else
        add(P(i,j),P(sx,sy),++inum);
    }
      }
  for (int i=1;i<=n;i++)
    for (int j=1;j<=m;j++)
      if ((~(i+j)&1) && !left[P(i,j)])
    dfs(P(i,j));
  for (int i=1;i<=n;i++)
    for (int j=1;j<=m;j++)
      if ((~(i+j)&1) && vst[P(i,j)] && Map[i][j]!='#')
    ans[++pnt]=abcd(i,j);
  sort(ans+1,ans+pnt+1);
  if (pnt==0)
    printf("LOSE\n");
  else{
    printf("WIN\n");
    for (int i=1;i<=pnt;i++)
      printf("%d %d\n",ans[i].first,ans[i].second);
  }
  return 0;
}

2437

首先空格的移动不可能出现环
因为先手是白方 我们把空格当作黑格
那么空格的移动 对应一条黑白相间的简单路径
我们只要建出二分图 判断这一路径是否唯一即可
因为这道题多次询问 需要删点操作 所以不能直接处理出必配点
只要每次删点后再增广一遍 看看最大匹配是否减小就能知道是否是先手必胜

#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#define cl(x) memset(x,0,sizeof(x))
using namespace std;

inline char nc(){
  static char buf[100000],*p1=buf,*p2=buf;
  return p1==p2&&(p2=(p1=buf)+fread(buf,1,100000,stdin),p1==p2)?EOF:*p1++;
}
inline void read(int &x){
  char c=nc(),b=1;
  for (;!(c>='0' && c<='9');c=nc()) if (c=='-') b=-1;
  for (x=0;c>='0' && c<='9';x=x*10+c-'0',c=nc()); x*=b;
}
inline void read(char *s){
  char c=nc(); int len=0;
  for (;!(c=='O' || c=='X' || c=='.');c=nc());
  for (;c=='O' || c=='X' || c=='.';s[++len]=c,c=nc()); 
}

#define U G[p].u
#define V G[p].v

const int N=1605;
const int dx[]={0,0,1,-1};
const int dy[]={1,-1,0,0};

int n,m;
#define P(x,y) (((x)-1)*m+y)
char Map[105][105];
int left[N],right[N];

struct edge{
  int u,v,next;
}G[N<<2];
int head[N],inum;
inline void add(int u,int v,int p){
  G[p].u=u; G[p].v=v; G[p].next=head[u]; head[u]=p;
}
inline void link(int u,int v){
  add(u,v,++inum),add(v,u,++inum);
}

int vst[N],del[N],boy[N];
inline bool dfs(int u){
  for (int p=head[u];p;p=G[p].next)
    if (!vst[V] && !del[V]){
      vst[V]=1;
      if (!boy[V] || dfs(boy[V]))
    return boy[V]=u,boy[u]=V,1;
    }
  return 0;
}

inline void Build(){
  int sx,sy;
  for (int i=1;i<=n;i++)
    for (int j=1;j<=m;j++)
      if ((i+j)&1)
    for (int k=0;k<4;k++){
      sx=i+dx[k],sy=j+dy[k];
      if (sx<1 || sy<1 || sx>n || sy>m) continue;
      if (Map[sx][sy]!=Map[i][j])
        link(P(i,j),P(sx,sy));
    }
  for (int i=1;i<=n;i++)
    for (int j=1;j<=m;j++)
      if (!boy[P(i,j)]){
    cl(vst);
    dfs(P(i,j));
      }
}

inline bool Jud(int x,int y){
  int u=P(x,y);
  del[u]=1;
  if (boy[u]){
    int tmp=boy[u];
    boy[u]=boy[tmp]=0;
    cl(vst);
    return !dfs(tmp);
  }else
    return 0;
}

int pnt,ans[N];

int main(){
  int Q,nx,ny,_x,_y;
  freopen("t.in","r",stdin);
  freopen("t.out","w",stdout);
  read(n); read(m);
  for (int i=1;i<=n;i++) read(Map[i]);
  for (int i=1;i<=n;i++)
    for (int j=1;j<=m;j++)
      if (Map[i][j]=='.') Map[i][j]='X',nx=i,ny=j;
  Build();
  read(Q);
  for (int i=1;i<=Q;i++){
    read(_x); read(_y);
    int t1=Jud(nx,ny),t2=Jud(_x,_y);
    if (t1 && t2)
      ans[++pnt]=i;
    read(nx); read(ny);
  }
  printf("%d\n",pnt);
  for (int i=1;i<=pnt;i++)
    printf("%d\n",ans[i]);
  return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值