【二分图匹配复习】 noi2011 game

 

        暑假做这道题时,纠结了N久=。=......最后跑到5机房来问秋哥和奥特曼,结果.......就陪着他们在看魁拔(>.<)......

        反正就是没有写啦。亡羊补牢,也算是对二分图匹配的复习吧。

      

        操作可以转化为路径问题,进行黑白染色之后,变成二分图。

        这道题的关键之处在于,先手必胜点是必定出现在最大匹配中的点。

        这个不难理解,一旦出现在最大匹配中,先手必定能沿着匹配边走,而后手只能沿着非匹配边走,最终无路可走。

 

        而如果兔兔操作时处于先手必胜点,操作后的点仍然是先手必胜点,它就必定犯了错。


         为了图思维方便,直接写了k次匈牙利,慢是慢了点,但是没有什么纠结的地方。

 

        

# include <cstdlib>
# include <cstdio>
# include <cstring>
 
using namespace std;
 
const int maxn=40+5;
int ans[1000+5],id[maxn][maxn], lk[maxn*maxn][maxn*maxn],point[maxn*maxn];
bool v[maxn*maxn], can[maxn*maxn];
int n,m,sx,sy,mx[4],my[4];
 
inline int abs(int x){return x<0?-x:x;};
 
void link(int x, int y){lk[x][++lk[x][0]]=y;}
 
void prepare()
{
    int i,j,k,ti,tj;
    for (i = 1; i <= n; i++)
      for (j = 1; j <= m; j++)
        if (abs(i-sx+j-sy) & 1) can[(i-1)*m+j]=(1==id[i][j]); 
          else can[(i-1)*m+j] = (-1==id[i][j]);
    for (i = 1; i <= n; i++)
      for (j = 1; j <= m; j++)
        if (can[(i-1)*m+j] && id[i][j]==-1)
          for (k = 0; k < 4; k++)
          {
                ti=i+mx[k], tj= j+my[k];
                if (can[(ti-1)*m+tj] && id[ti][tj]==1) 
                   link((i-1)*m+j, (ti-1)*m+tj);
          }
}
 
bool dfs(int x)
{
    int i,ne; v[x]=true;
    for (i = 1; i <= lk[x][0]; i++)
    {
        ne=lk[x][i];
        if ((!v[ne])&&can[ne])
        {
            v[ne]=true;
            if (point[ne]==0||dfs(point[ne]))
            {point[ne]= x; return true;}
        }
    }
    return false;
}
 
bool check(int x, int y)
{
    int i,j,sum=0;
    memset(point,0,sizeof(point));
    for (i = 1;i <= n; i++)
      for (j = 1; j <= m; j++)
        if (can[(i-1)*m+j] && id[i][j]==-1)
        {
          memset(v,false,sizeof(v));
          if (dfs((i-1)*m+j)) sum++;
        }
    memset(point,0,sizeof(point));
    can[(x-1)*m+y]=false;
    for (i = 1;i <= n; i++)
      for (j = 1; j <= m; j++)
        if (can[(i-1)*m+j] && id[i][j]==-1)
        {
          memset(v,false,sizeof(v));
          if (dfs((i-1)*m+j)) sum--;
        }
    if (sum) return true; else return false;
}
 
int main()
{
    int test,i,j,x,y; char c;
    //freopen("game.in", "r", stdin);
    //freopen("game.out", "w", stdout);
    scanf("%d%d\n", &n, &m);
    mx[0]=1;mx[1]=0;mx[2]=-1;mx[3]=0;
    my[0]=0;my[1]=1;my[2]=0;my[3]=-1;
    for (i = 1; i <=n; i++)
    {
      for (j =1; j <=m; j++)
      {
            scanf("%c", &c);
            if (c=='O') id[i][j] = 1;
            else if (c=='X') id[i][j] = -1;
            else sx=i,sy=j, id[i][j]=-1;
      }
      scanf("\n");
    }
    prepare();
    scanf("%d", &test);
    for (i = 1; i <= test; i++)
    {
        scanf("%d%d", &x, &y);
        bool win=check(sx,sy); can[(sx-1)*m+sy]=false;
        sx=x,sy=y;
        scanf("%d%d", &x, &y);
        if (win && check(sx,sy)) ans[++ans[0]]=i; can[(sx-1)*m+sy]=false;
        sx=x,sy=y;
    }
    printf("%d\n", ans[0]);
    for (i = 1; i <= ans[0]; i++)
       printf("%d\n", ans[i]);
    return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值