zoukankan      html  css  js  c++  java
  • [NOI2011]兔兔与蛋蛋游戏 二分图博弈

    题面

    题面

    题解

    通过观察,我们可以发现如下性质:

    • 可以看做是2个人在不断移动空格,只是2个人能移动的边不同
    • 一个位置不会被重复经过 : 根据题目要求,因为是按黑白轮流走,所以不可能重复经过一个点,不然就变成一个人连续走2次了
    • 原图是一个二分图 : 也是由按黑白轮流走这个要求得到的
      因此我们对原图按照与原点的距离进行黑白染色,再跑二分图博弈即可。
    #include<bits/stdc++.h>
    using namespace std;
    #define R register int
    #define AC 45
    #define ac 5000
    
    int n, m, sx, sy, all;
    int id[AC][AC], ans[ac], link[ac];
    int a[6] = {-1, 1, 0, 0}, b[6] = {0, 0, -1, 1};
    bool can[AC][AC], z[ac], vis[ac];
    char s[AC][AC];
    
    struct node{int x, y;}back[ac];
    
    inline int read()
    {
        int x = 0;char c = getchar();
        while(c > '9' || c < '0') c = getchar();
        while(c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar();
        return x;
    }
    
    bool check(int x, int y)//起点是黑色
    {
        int tmp = abs(x - sx) + abs(y - sy);
        if((tmp & 1) && s[x][y] == 'O') return 1;//相距奇数格,则为白格,需要白色
        else if(!(tmp & 1) && s[x][y] == 'X') return 1;
        else if(s[x][y] == '.') return 1;
        return 0;
    }
    
    bool dfs(int x)
    {
        for(R i = 0; i < 4; i ++)
        {
            int xx = back[x].x + a[i], yy = back[x].y + b[i], ID = id[xx][yy];
            if(xx <= 0 || yy <= 0 || xx > n || yy > m) continue;
            if(!can[xx][yy] || vis[ID]) continue;
            vis[ID] = true;
            if(!link[ID] || dfs(link[ID])) 
            {
                link[ID] = x, link[x] = ID;
                return true;
            }
        }
        return false;
    }
    
    void cal()
    {
        int tmp = (sx + sy) % 2;
        for(R i = 1; i <= all; i ++)
        {
            int x = back[i].x, y = back[i].y;
            if((x + y) % 2 != tmp || !can[x][y]) continue;//和为奇数则在T集合
            memset(vis, 0, sizeof(vis)), dfs(i);
        }
    }
    
    bool dfs1(int x)
    {
        for(R i = 0; i < 4; i ++)
        {
            int xx = back[x].x + a[i], yy = back[x].y + b[i], ID = id[xx][yy];
            if(xx <= 0 || yy <= 0 || xx > n || yy > m) continue;
            if(!can[xx][yy] || vis[ID]) continue;
            vis[ID] = true;
            if(!link[ID] || dfs(link[ID])) return true;
        }
        return false;
    }
    
    void pre()
    {
        n = read(), m = read(), all = n * m;
        int tmp1 = 1, tmp2 = 2;
        for(R i = 1; i <= n; i ++) 
        {
            scanf("%s", s[i] + 1);
            for(R j = 1; j <= m; j ++)
            {
                if(s[i][j] == '.') sx = i, sy = 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 ++) can[i][j] = check(i, j);
    }
    
    void check_()
    {
        for(int i = 1; i <= n; i ++) 
        {
        	for(int j = 1; j <= m; j ++) printf("%d ", can[i][j]);
        	printf("
    ");
        }
        
        for(R i = 1; i <= n; i ++) 
        {
        	for(R j = 1; j <= m; j ++) printf("%d ", link[id[i][j]]);
        	printf("
    ");
        }
    }
    
    void work()
    {
        int T = read() << 1, x, y, ID, tmp; bool done;
        for(R i = 0; i <= T; i ++)
        {
            for(R j = 1; j <= all; j ++) z[j] = 0;
            if(i) x = read(), y = read(), ID = id[x][y];
            else x = sx, y = sy, ID = id[x][y];
            can[x][y] = 0;//这个要在一开始就修改
            if(!link[ID]) continue;//搜S/T集合中有没有可到达的同侧未匹配点来取代它,因为直接搜不太方便,所以直接搜对面的匹配点是否可以找到增广路
            memset(vis, 0, sizeof(vis));
            done = dfs(link[ID]);//找到了说明这个点不是必须点
            tmp = link[ID];//所以搜这个点的匹配点是否可以找到对面的一个未匹配点(反向增广)
            if(done) link[ID] = 0;//清空这个点的匹配,因为这个点已经到过了,所以就不能到达了,如果已经匹配上了就不能改了
            else if(!done) link[ID] = link[tmp] = 0, ans[i] = true;//没有可取代点就先手必胜
        }
        /*for(R i = 0; i <= T; i ++) printf("%d ", ans[i]);
        printf("
    ");*/
        int rnt = 0;
        for(R i = 0; i <= T; i += 2)
            if(ans[i] == 1 && ans[i + 1] == 1) ++ rnt;
        printf("%d
    ", rnt);
        for(R i = 0; i <= T; i += 2)
            if(ans[i] == 1 && ans[i + 1] == 1) printf("%d
    ", (i + 2) >> 1);
    }
    
    int main()
    {
    //	freopen("in.in", "r", stdin);
        pre();
        cal();
        work();
    //	fclose(stdin);
        return 0;
    }
    
  • 相关阅读:
    学习Java的知识体系路线(详细完整版,附图加目录)
    数组的定义和使用,理解多维数组和Array类
    Java运算符使用总结(重点:自增自减、位运算和逻辑运算)
    Java常用修饰符总结
    实例/静态变量、局部变量和常量的定义及其作用域
    Nothing is impossible
    科班学习java遇到瓶颈,每天云里雾里怎么办?
    人生路漫漫,相见不如不见
    基于视频的车辆识别
    编译原理课后习题答案令A,B和C是任意正规式,证明以下关系成立(A|B)*=(A*B*)*=(A*|B*)*
  • 原文地址:https://www.cnblogs.com/ww3113306/p/10341912.html
Copyright © 2011-2022 走看看