[Noi2011]兔兔与蛋蛋

描述

这些天,兔兔和蛋蛋喜欢上了一种新的棋类游戏。

这个游戏是在一个 n 行 m 列的棋盘上进行的。游戏开始之前,棋盘上有一个格子是空的,其它的格子中都放置了一枚棋子,棋子或者是黑色,或者是白色。

每一局游戏总是兔兔先操作,之后双方轮流操作,具体操作为:

  • 兔兔每次操作时,选择一枚与空格相邻的白色棋子,将它移进空格。
  • 蛋蛋每次操作时,选择一枚与空格相邻的黑色棋子,将它移进空格。

第一个不能按照规则操作的人输掉游戏。为了描述方便,下面将操作“将第x行第y列中的棋子移进空格中”记为 M(x,y)。

例如下面是三个游戏的例子。

image.png

image.png

image.png

最近兔兔总是输掉游戏,而且蛋蛋格外嚣张,于是兔兔想请她的好朋友——你——来帮助她。她带来了一局输给蛋蛋的游戏的实录,请你指出这一局游戏中所有她“犯错误”的地方。

注意:

  • 两个格子相邻当且仅当它们有一条公共边。
  • 兔兔的操作是“犯错误”的,当且仅当,在这次操作前兔兔有必胜策略,而这次操作后蛋蛋有必胜策略。

输入描述

输入的第一行包含两个正整数 n,m。

接下来 n 行描述初始棋盘。其中第 i 行包含 m 个字符,每个字符都是大写英文字母 X、大写英文字母 O 或点号 . 之一,分别表示对应的棋盘格中有黑色棋子、有白色棋子和没有棋子。其中点号 . 恰好出现一次。

接下来一行包含一个整数 k(1≤k≤1000) ,表示兔兔和蛋蛋各进行了 k 次操作。

接下来 2k 行描述一局游戏的过程。其中第 2i−1 行是兔兔的第 i 次操作(编号为 i 的操作) ,第 2i 行是蛋蛋的第 i 次操作。每个操作使用两个整数 x,y 来描述,表示将第 x 行第 y 列中的棋子移进空格中。

输入保证整个棋盘中只有一个格子没有棋子, 游戏过程中兔兔和蛋蛋的每个操作都是合法的,且最后蛋蛋获胜。

输出描述

输出文件的第一行包含一个整数 r,表示兔兔犯错误的总次数。

接下来 r 行按递增的顺序给出兔兔“犯错误”的操作编号。其中第 i 行包含一个整数 ai​ 表示兔兔第 i 个犯错误的操作是他在游戏中的第 ai​ 次操作。

样例输入 1 

1 6 
XO.OXO 
1 
1 2 
1 1 

样例输出 1 

1
1

样例输入 2 

3 3 
XOX 
O.O 
XOX 
4 
2 3 
1 3 
1 2 
1 1 
2 1 
3 1 
3 2 
3 3 

样例输出 2 

0

样例输入 3 

4 4 
OOXX 
OXXO 
OO.O 
XXXO 
2 
3 2 
2 2 
1 2 
1 3 

样例输出 3 

2
1
2

提示

数据范围与提示


对于 100% 的数据,1≤n≤40,1≤m≤40,1≤k≤1000。

测试点编号nm
1,2n=11≤m≤20
3n=3m=4
4,5n=4m=4
6,7n=4m=5
8n=3m=7
9∼14n=21≤m≤40
15,161≤n≤161≤m≤16
17∼201≤n≤401≤m≤40

分析 

题解:我们将棋盘分成黑白格子(相邻格子颜色不同),将空格染成黑色,那么我们可以知道X为合法棋子当且仅当X在黑色格子,而O为合法棋子当且仅当O在白色格子。

相邻合法棋子(空格也为合法)连边,则我们得到无向图,而它也是二部图,当空格一定是在最大匹配中时,则先手必胜;否则先手必败。若不一定在,则空格可能在偶数条交错路,或者有点可以代替空格(此时空格可不在最大匹配中),那么此时后手总有办法走到先手所在的部,即先手无法移动。

判断空格是否在最大匹配中,若空格没有对应的匹配点,那么空格肯定不在最大匹配中;若有匹配点,则把空格标记掉,同时将空格和对应匹配点的信息标为空,搜索匹配点看能否增大匹配数,若能则不一定在最大匹配中。

                        

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <cmath>
#include <algorithm>
#include <vector>
 
using namespace std;
 
const int N=45;
char str[N][N];
int num[N][N],cnt,tot;
int del[N*N],vis[N*N],match[N*N],res[N*N];
int Ans[2000];
int xx,yy;
int n,m;
vector<int>V[N*N];
bool dfs(int u){
    for (int i=0;i<int(V[u].size());i++){
        int v=V[u][i];
        if (del[v] || vis[v]==tot)continue;
        vis[v]=tot;
        if (match[v]==0 || dfs(match[v])){
            match[v]=u;match[u]=v;
            return true;
        }
    }
    return false;
}
void Add(int u,int v){
    V[u].push_back(v);V[v].push_back(u);
}
int main()
{
    //freopen("1.txt","r",stdin);
    scanf("%d%d",&n,&m);
    for (int i=1;i<=n;i++){
        scanf("%s",str[i]+1);
        for (int j=1;j<=m;j++)if (str[i][j]=='.')
            {xx=i;yy=j;}
    }
    str[xx][yy]='X';
    memset(num,0,sizeof num);
    cnt=0;
    for (int i=1;i<=n;i++)for (int j=1;j<=m;j++){
            if (str[i][j]=='X' && (abs(xx-i)+abs(yy-j))%2==0)
                cnt++,num[i][j]=cnt;
            if (str[i][j]=='O' && (abs(xx-i)+abs(yy-j))%2==1)
                cnt++,num[i][j]=cnt;
    }
    for (int i=1;i<=cnt;i++)V[i].clear();
    for (int i=1;i<=n;i++)for (int j=(i%2==0)?2:1;j<=m;j+=2){
        if (j>1 && num[i][j-1])Add(num[i][j],num[i][j-1]);
        if (j<m && num[i][j+1])Add(num[i][j],num[i][j+1]);
        if (i>1 && num[i-1][j])Add(num[i][j],num[i-1][j]);
        if (i<n && num[i+1][j])Add(num[i][j],num[i+1][j]);
    }
    memset(vis,0,sizeof vis);
    memset(del,0,sizeof del);
    memset(match,0,sizeof match);
    tot=0;
    for (int i=1;i<=n;i++)for (int j=(i%2==0)?2:1;j<=m;j+=2)/*if (num[i][j] && !match[num[i][j]])*/{//只能从二分图的一边搜,为什么不能加/**/里面的部分
        tot++;dfs(num[i][j]);
    }
    int Q;scanf("%d",&Q);Q<<=1;
    for (int i=1;i<=Q;i++){
        if (match[num[xx][yy]]==0){
            res[i]=0;//i走之前预判必败还是必胜,0表示必败
            del[num[xx][yy]]=1;
        }else{
            int tmp;
            match[tmp=match[num[xx][yy]]]=0;match[num[xx][yy]]=0;
            del[num[xx][yy]]=1;
            tot++;res[i]=!dfs(tmp);
        }
        scanf("%d%d",&xx,&yy);
    }
    int gs=0;
    for (int i=1;i<=Q;i+=2)if (res[i] && res[i+1])gs++,Ans[gs]=(i+1)>>1;
    printf("%d\n",gs);
    for (int i=1;i<=gs;i++)printf("%d\n",Ans[i]);
    return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值