• bzoj2756 [SCOI2012]奇怪的游戏


    Description

    Blinker最近喜欢上一个奇怪的游戏。
    这个游戏在一个 N*M 的棋盘上玩,每个格子有一个数。每次 Blinker 会选择两个相邻的格子,并使这两个数都加上 1。
    现在 Blinker 想知道最少多少次能使棋盘上的数都变成同一个数,如果永远不能变成同一个数则输出-1。

    Input

    输入的第一行是一个整数T,表示输入数据有T轮游戏组成。
    每轮游戏的第一行有两个整数N和M, 分别代表棋盘的行数和列数。
    接下来有N行,每行 M个数。

    Output

    对于每个游戏输出最少能使游戏结束的次数,如果永远不能变成同一个数则输出-1。

    Sample Input
    2
    2 2
    1 2
    2 3
    3 3
    1 2 3
    2 3 4
    4 3 2

    Sample Output
    2
    -1

    HINT
    【数据范围】
    对于30%的数据,保证 T<=10,1<=N,M<=8
    对于100%的数据,保证 T<=10,1<=N,M<=40,所有数为正整数且小于1000000000

    分析:
    这道题需要分情况讨论:
    首先要明确,黑块和白块是捆绑销售的,所以黑块一共增加了多少,白块就增加了多少

    情况一:

    n*m是偶数
    那么一定可以用骨牌(两格)不重不漏的覆盖所有棋盘
    若X可行,那么X+1一定可行,这符合二分的条件,所以二分+判定

    情况二:

    n*m是奇数
    这个时候就有方程:
    X(达到的相等数值)
    Bs 黑块上的原始数字之和 Bn 黑块数目
    Ws 白块上的原始数字之和 Wn 白块数目

    X*Bn-Bs=X*Wn-Ws

    解出X,用网络流判断是否可行

    注意一下二分的下界是棋盘中的最大值
    (棋盘中的数只加不减)

    简单说一下建图吧:
    把棋盘黑白染色,黑色格子作为第一部,白色格子作为第二部
    源点向一部连边,容量是二分(计算)出的答案-z[i][j]
    二部向汇点连边,容量是二分(计算)出的答案-z[i][j]
    在两个部之间,相邻格子连容量为INF的边

    tip

    开ll

    不保证代码的正确性,但是这个网站上的代码是对的
    http://www.cnblogs.com/jianglangcaijin/p/3799797.html

    这里写代码片
    #include<cstdio>
    #include<cstring>
    #include<iostream>
    #define ll long long
    
    using namespace std;
    
    const int N=2005;
    const ll INF=0x33333333;
    struct node{
        int x,y,nxt;
        ll v;
    };
    node way[20001];
    int tot,st[N],cur[N],deep[N],s,t;
    int q[N],tou,wei,n,m,Bn,Wn;
    ll Bs,Ws,z[45][45],mx;
    
    inline void add(int u,int w,ll z)
    {
        tot++;
        way[tot].x=u;way[tot].y=w;way[tot].v=z;way[tot].nxt=st[u];st[u]=tot;
        tot++;
        way[tot].x=w;way[tot].y=u;way[tot].v=0;way[tot].nxt=st[w];st[w]=tot;
    }
    
    inline int get(int x,int y){return (x-1)*n+y;}
    
    inline ll max(ll a,ll b){if (a>b) return a;else return b;}
    
    int bfs(int s,int t)
    {
        memset(deep,-1,sizeof(deep));
        tou=wei=0;
        q[++wei]=s;
        deep[s]=1;
        for (int i=s;i<=t;i++) cur[i]=st[i];
        do
        {
            int r=q[++tou];
            for (int i=st[r];i!=-1;i=way[i].nxt)
                if (way[i].v&&deep[way[i].y]==-1)
                {
                    deep[way[i].y]=deep[r]+1;
                    q[++wei]=way[i].y;
                }
        }while (tou<wei);
        return deep[t]!=-1;
    }
    
    ll dfs(int now,int t,ll limit)
    {
        if (!limit||now==t) return limit;
        int i;
        ll flow=0,f;
        for (i=cur[now];i!=-1;i=way[i].nxt)
        {
            cur[now]=i;
            if (deep[way[i].y]==deep[now]+1&&way[i].v&&(f=dfs(way[i].y,t,min(limit,way[i].v))))
            {
                flow+=f;
                limit-=f;
                way[i].v-=f;
                way[i^1].v+=f;
                if (!limit) break;
            }
        }
        return flow;
    }
    
    ll doit(ll num)
    {
        ll ans=0;
        while (bfs(s,t))
            ans+=dfs(s,t,INF);
        return ans==num;  //与目标流量一样 
    }
    
    int check(ll x)
    {
        memset(st,-1,sizeof(st));
        tot=-1;
        int i,j;
        for (int i=1;i<=n;i++)
            for (int j=1;j<=m;j++)
            {
                if (x<z[i][j]) return 0;
                if ((i+j)%2==0)  //B
                {
                    add(s,get(i,j),x-z[i][j]);
                    if (i>1) add(get(i,j),get(i-1,j),INF);
                    if (j>1) add(get(i,j),get(i,j-1),INF);
                    if (i<n) add(get(i,j),get(i+1,j),INF);
                    if (j<m) add(get(i,j),get(i,j+1),INF);
                }
                else add(get(i,j),t,x-z[i][j]);
            }
        if (doit(x*Bn-Bs)) return 1;
        else return 0;
    }
    
    ll erfen()
    {
        if (Bs!=Ws) return -1;
        ll l=mx,r=INF;
        ll mid;
        while (l<r)
        {
            mid=(l+r)>>1;
            if (check(mid))
               r=mid;
            else l=mid+1;
        }
        return l*Bn-Bs;
    }
    
    ll solve()
    {
        s=0;t=n*m+1;
        if (n*m%2==0)
           return erfen();
        else
        {
            ll X=(Bs-Ws)/(Bn-Wn);
            if (check(X)) return Bn*X-Bs;
            else return -1;
        }
    }
    
    int main()
    {
        int T;
        scanf("%d",&T);
        while (T--)
        {
            scanf("%d%d",&n,&m);
            for (int i=1;i<=n;i++)
                for (int j=1;j<=m;j++)
                {
                    scanf("%lld",&z[i][j]); 
                    if ((i+j)%2==0) Bs+=z[i][j],Bn++;   //黑块上的数和 
                    else Ws+=z[i][j],Wn++;
                    mx=max(mx,z[i][j]);   //二分下限,棋盘上的数只加不减 
                }
            printf("%lld",solve());
        }
    }
  • 相关阅读:
    Swift
    遇到奇怪的问题,帮助威猛答案,表单提交的文件提交的无限数据问题
    对于思考小端和大端字节顺序
    圈真的决定你的未来?
    坦克大战
    android visible invisible和gone差异
    编程之美2: 二进制重建
    WCF分布式事务(EF)
    Android+NDK+OpenGLES开发环境配置
    struts2和struts1认识
  • 原文地址:https://www.cnblogs.com/wutongtong3117/p/7673435.html
Copyright © 2020-2023  润新知