[JSOI2009]游戏 二分图博弈

题面

题面

题解

二分图博弈的模板题,只要会二分图博弈就可以做了,可以当做板子打。
根据二分图博弈,如果一个点x在某种方案中不属于最大匹配,那么这是一个先手必败点。
因为对方先手,因此我们就是要找这样一个点。
观察点x的性质,对于这样一个点x,我们一定可以找到一个点来代替它的位置,而什么样的点可以代替它呢?
从x出发,能够到达的未匹配同侧点可以,只需要交换匹配边即可。
因此做几遍dfs就可以了

#include<bits/stdc++.h>
using namespace std;
#define R register int
#define AC 110
#define ac 81000//向四周连边

int n, m, num;
int Head[ac], Next[ac], date[ac], tot;
int link[ac], match[ac], id[AC][AC], a[6] = {-1, 1, 0, 0}, b[6] = {0, 0, -1, 1};
char s[AC][AC];
bool vis[ac], z[ac];

struct node{int x, y;}back[ac];

inline void add(int f, int w)
{
    date[++ tot] = w, Next[tot] = Head[f], Head[f] = tot;
    date[++ tot] = f, Next[tot] = Head[w], Head[w] = tot;
}

bool dfs(int x)
{
    for(R i = Head[x]; i; i = Next[i])
    {
        int now = date[i];
        if(vis[now]) continue;
        vis[now] = true;
        if(!link[now] || dfs(link[now])) 
        {
            link[now] = x, match[x] = now; 
            return 1;
        }
    }
    return 0;
}

void cal()
{
    memset(link, 0, sizeof(link));
    for(R i = 1; i <= num; i ++)
    {
        if(!(i & 1)) continue;//每次都是从S集合出发的
        memset(vis, 0, sizeof(vis)), dfs(i);//不需要知道最大匹配的大小,只需要知道某一种合法方案即可
    }	
}

void pre()
{
    scanf("%d%d", &n, &m), num = n * m;
    for(R i = 1; i <= n; i ++) scanf("%s", s[i] + 1);
    int tmp1 = 1, tmp2 = 2;
    for(R i = 1; i <= n; i ++)
    {
        for(R j = 1; j <= m; j ++)
        {
            if((i + j) & 1) id[i][j] = tmp2, back[tmp2] = (node){i, j}, tmp2 += 2;
            else id[i][j] = tmp1, back[tmp1] = (node){i, j}, tmp1 += 2;
        }
    }
    
    for(R i = 1; i <= n; i ++)
        for(R j = 1; j <= m; j ++)
        {
            if(s[i][j] == '#') continue;
            for(R k = 0; k <= 3; k ++)
            {
                int x = i + a[k], y = j + b[k];
                if(x <= 0 || x > n || y <= 0 || y > m || s[x][y] == '#') continue;
                int s = id[i][j], t = id[x][y];
                (s & 1) ? add(s, t) : add(t, s);//由编号为奇数的点向编号为偶数的点连边
            }
        }
}

void dfs1(int x)
{
    if(z[x]) return ;
    z[x] = true;
    for(R i = Head[x]; i; i = Next[i]) dfs1(link[date[i]]);//这样才能保证dfs到的是同侧的点,
}//只有同侧的点才能保证经过的匹配边和非匹配边数量相同,可以互相交换

void dfs2(int x)
{
    if(z[x]) return ;
    z[x] = true;
    for(R i = Head[x]; i; i = Next[i]) dfs2(match[date[i]]);
}

void work()
{
    for(R i = 1; i <= num; i ++) 
    {
        if(s[back[i].x][back[i].y] == '#') continue;
        if((i & 1) && !match[i]) dfs1(i);
        else if(!(i & 1) && !link[i]) dfs2(i);//注意要从同侧的非匹配点dfs到同侧的匹配点,这样才能互换方案
    }
    bool done = false;
    for(R i = 1; i <= n; i ++)
        for(R j = 1; j <= m; j ++)
            if(z[id[i][j]]) {done = true; break;}
    if(!done) {printf("LOSE\n"); return ;}
    printf("WIN\n");
    for(R i = 1; i <= n; i ++) 
        for(R j = 1; j <= m; j ++)
            if(z[id[i][j]]) printf("%d %d\n", i, j);
        
/*	for(R i = 1; i <= num; i ++) if(z[i]) {done = true; break;}
    if(!done) {printf("LOSE\n"); return ;}
    printf("WIN\n");
    for(R i = 1; i <= num; i ++) 
        if(z[i]) printf("%d %d\n", back[i].x, back[i].y);*/
}

int main()
{
//	freopen("in.in", "r", stdin);
    pre();
    cal();
    work();
//	fclose(stdin);
    return 0;
}
posted @ 2019-01-31 14:35  ww3113306  阅读(335)  评论(0编辑  收藏  举报
知识共享许可协议
本作品采用知识共享署名-非商业性使用-禁止演绎 3.0 未本地化版本许可协议进行许可。