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

解题思路:

本题不容易看出是网络流,仔细分析,发现可以二分答案最后每个格子最后的值,然后验证。但是要分格子总数奇偶性分类考虑。
分析如下:
由于每次操作都是相邻的两个,所以可以考虑将棋盘黑白染色,这样我们可以对黑色的格子和白色的格子单独考虑。
设黑色格子个数为 cnt1,总和为 sum1,白色格子个数为 cnt2,总和为 sum2,最终所有格子都变成了 x,则很容易写出下列的关系式:
cnt1 x - sum1 = cnt2 x - sum2
=> x * (cnt1 - cnt2) = sum1 - sum2
=> x = (sum1 - sum2) / (cnt1 - cnt2)

现在讨论cnt1 与 cnt2的情况:
1、若cnt1 = cnt2,则说明在sum1 = sum2的情况下,若x可行,则x+1同样可以,这样我们可以通过二分的方法找出最小的满足条件的x,求出最小步数;若sum1 != sum2则无解。
2、若cnt1!= cnt2,则我们解出了一个x,这个x必须要满足三个条件:
(1)x必须为整数
(2)x不小于所有格子的最大值
(3)x必须要通过检验
如果x满足这三个条件,则我们可以解出最小步数,否则无解。

那么我们怎样检验x是否可行呢,这个需要通过网络流来验证。

设map[i][j]为格子对应的数字,对于每个黑色的格子,从源点连一条边,容量为x - map[i][j],并且对它周围的四个白色格子连一条边容量为inf;对于每个白色格子,向汇点连一条边,容量为x - map[i][j]

设最大流为maxflow,若maxflow = (n * m * x - sum1 - sum2) >> 1的话,说明存在方案可以使所有数变成x,同时最小步数minstep = (n * m * x - sum1 - sum2) >> 1
注意到格子的数可能很大,所以要使用long long类型

这样就解决了本题。

#include<bits/stdc++.h>
#define ll long long
using namespace std;

int getint()
{
    int i=0,f=1;char c;
    for(c=getchar();(c!='-')&&(c<'0'||c>'9');c=getchar());
    if(c=='-')f=-1,c=getchar();
    for(;c>='0'&&c<='9';c=getchar())i=(i<<3)+(i<<1)+c-'0';
    return i*f; 
}

const int N=5000,M=50000;
const ll INF=1ll<<50;
const int dx[4]={-1,0,1,0};
const int dy[4]={0,-1,0,1};
int S,T,n,m,mx,cnt,p[50][50],a[50][50],c[50][50];ll sum[2];
int tot=1,dis[N],cur[N],first[N],nxt[M],to[M];ll cap[M];
queue<int>q;

void add(int x,int y,ll z)
{
    nxt[++tot]=first[x],first[x]=tot,to[tot]=y,cap[tot]=z;
    nxt[++tot]=first[y],first[y]=tot,to[tot]=x,cap[tot]=0;
}

bool bfs()
{
    while(!q.empty())q.pop();
    for(int i=S;i<=T;i++)dis[i]=-1,cur[i]=first[i];
    q.push(S),dis[S]=0;
    while(!q.empty())
    {
        int u=q.front();q.pop();
        for(int e=first[u];e;e=nxt[e])
        {
            int v=to[e];
            if(dis[v]==-1&&cap[e])
            {
                dis[v]=dis[u]+1,q.push(v);
                if(v==T)return true;
            }
        }
    }
    return false;
}

ll dinic(int u,ll flow)
{
    if(u==T)return flow;
    ll res=0;
    for(int &e=cur[u];e;e=nxt[e])
    {
        int v=to[e];
        if(dis[v]==dis[u]+1&&cap[e])
        {
            ll det=dinic(v,min(flow-res,cap[e]));
            cap[e]-=det,cap[e^1]+=det;
            res+=det;if(res==flow)break;
        }
    }
    if(res<flow)dis[u]=-1;
    return res;
}

bool check(ll lim)
{
    tot=1,memset(first,0,sizeof(first));
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++)
        if(c[i][j])
        {
            add(S,p[i][j],lim-a[i][j]);
            for(int k=0;k<4;k++)
            {
                int x=i+dx[k],y=j+dy[k];
                if(x<1||x>n||y<1||y>m)continue;
                add(p[i][j],p[x][y],INF);
            }
        }
        else add(p[i][j],T,lim-a[i][j]);
    ll res=0;
    while(bfs())res+=dinic(S,INF);
    return res==(1ll*n*m*lim-sum[0]-sum[1])/2;
}

int main()
{
    //freopen("lx.in","r",stdin);
    for(int t=getint();t;t--)
    {
        cnt=sum[0]=sum[1]=mx=0;
        memset(c,-1,sizeof(c));
        n=getint(),m=getint();
        for(int i=1;i<=n;i++)
            for(int j=1;j<=m;j++)
            {
                p[i][j]=++cnt,a[i][j]=getint();
                int col=c[i][j]=(i+j)&1;
                sum[col]+=a[i][j],mx=max(mx,a[i][j]);
            }
        S=0,T=++cnt;
        if(n*m%2==0)
        {
            if(sum[0]!=sum[1]){puts("-1");continue;}
            ll l=mx,r=1e15,ans=-1;
            while(l<=r)
            {
                ll mid=l+r>>1;
                if(check(mid))ans=mid,r=mid-1;
                else l=mid+1;
            }
            ans==-1?puts("-1"):printf("%lld\n",(1ll*n*m*ans-sum[0]-sum[1])/2);
        }
        else
        {
            ll x=sum[0]-sum[1];
            if(x>=mx&&check(x))printf("%lld\n",(1ll*n*m*x-sum[0]-sum[1])/2);
            else puts("-1");
        }
    }
    return 0;
}
1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看rEADME.md或论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 5、资源来自互联网采集,如有侵权,私聊博主删除。 6、可私信博主看论文后选择购买源代码。 1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看rEADME.md或论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 5、资源来自互联网采集,如有侵权,私聊博主删除。 6、可私信博主看论文后选择购买源代码。 1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md或论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 5、资源来自互联网采集,如有侵权,私聊博主删除。 6、可私信博主看论文后选择购买源代码。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值