BZOJ2756 【scoi2012】奇怪的游戏(二分+网络流)

时间限制:1秒  内存限制:64M

【问题描述】

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

【输入格式】

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

【输出格式】

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

【输入样例】

2
2 2
1 2
2 3
3 3
1 2 3
2 3 4
4 3 2

【输出样例】

2
-1

【数据范围】

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

【来源】

BZOJ2756

这道题很明显是一道网络流但是要做起来就麻烦了,首先我们要把点分成黑白点(二分图的普遍做法),建图就是白进黑出(具体的容量下文会提到),然后白向黑连边。
图是建立好了,但要做这道题我们还差一个很关键的东西,就是题中要的那个要变成的数,很多人会直接用最大的那个,但其实并不是。

我们可以来算一下那个值的可能最小值:
我们设这个值为x,白点的数为x1,黑点的数为x2,白点的权值和为y1,黑点的权值和为y2.
所以:x*x1-y1=x*x2-y2.(因为白点加一次黑点就要加一次)
但最后的答案已经不是这个x,因为会出现1白1黑没到x,但又刚好不能挨着,而其他点已经到x不能加的情况。
但我们可以证明如果黑白点数量不一样那么如果x不行,其他的大于x的值也不行。
那接下来我们就只需要考虑黑白点数量一样的情况了。
对于这种情况我们可以证明只要k成立那么所有大于k的值都可以成立。
到这里你想到了什么?
我们伟大的二分拼人品大法,接下来就是2分的范围了。
而没时间的我直接用最大点的值加了个100,再在这个值与最大值直接2分猜答案了一波。事实证明可以秒过而且不会错。

代码如下:

#include<cstdlib>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<vector>
using namespace std;
typedef long long ll;
const int maxn=45;
const ll inf=20000000ll;

struct edge
{
    int u,v;
    ll f,c;
};
vector<edge>e;
vector<int>g[maxn*maxn];
int q[maxn*maxn*5],cur[maxn*maxn],d[maxn*maxn],n,m,a[maxn][maxn],s,t,cnt=-1;
ll tot;
bool ok=0;
int dx[]={0,-1,0,1};
int dy[]={-1,0,1,0};
int vis[maxn][maxn];
ll num[3],sum[3];

void dfs2(int i,int j,int tt)
{
    vis[i][j]=tt;
    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||vis[x][y]) continue;
        dfs2(x,y,3-tt);
    }
}
void add(int u,int v,ll f)
{
    e.push_back((edge){u,v,f,0});
    g[u].push_back(++cnt);
    e.push_back((edge){v,u,0,0});
    g[v].push_back(++cnt);
}
int ask(int i,int j)
{
    return (i-1)*m+j;
}
bool bfs()
{
    int root=0,frond=0;
    memset(d,0,sizeof(d));
    q[root++]=s;
    d[s]=1;
    while(root!=frond)
    {
        int i=q[frond++];
        int tt=g[i].size();
        for(int k=0;k<tt;k++)
        {
            int id=g[i][k],j=e[id].v;
            if(e[id].c==e[id].f||d[j]) continue;
            d[j]=d[i]+1;
            q[root++]=j;
        }
    }
    return d[t]!=0;
}
ll dfs(int i,ll a)
{
    if(i==t||a==0) return a;
    ll f,flow=0;
    int tt=g[i].size();
    for(int &k=cur[i];k<tt;k++)
    {
        int id=g[i][k],j=e[id].v;
        if(d[j]==d[i]+1&&(f=dfs(j,min(a,e[id].f-e[id].c)))>0)
        {
            a-=f;
            flow+=f;
            e[id].c+=f;
            e[id^1].c-=f;
            if(!a) break;
        }
    }
    return flow;
}
ll dinic()
{
    ll flow=0;
    while(bfs())
    {
        memset(cur,0,sizeof(cur));
        flow+=dfs(s,inf);
    }
    return flow;
}
void work(int maxv)
{
    cnt=-1;
    e.clear();
    for(int i=s;i<=t;i++)
    g[i].clear();
    for(int i=1;i<=n;i++)
    for(int j=1;j<=m;j++)
    {
        if(a[i][j]<maxv) 
        {
            if(vis[i][j]==2)add(ask(i,j),t,maxv-a[i][j]);
            if(vis[i][j]==1)
            {
            add(s,ask(i,j),maxv-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(ask(i,j),ask(x,y),200000000ll);
            }
            }
        }
    }
}
void init()
{
    int maxv=0;
    scanf("%d%d",&n,&m);
    s=0,t=n*m+1;
    memset(vis,0,sizeof(vis));
    memset(num,0,sizeof(num));
    memset(sum,0,sizeof(sum));
    dfs2(1,1,1);
    for(int i=1;i<=n;i++)
    for(int j=1;j<=m;j++) 
    {
        scanf("%d",&a[i][j]);
        num[vis[i][j]]++;
        sum[vis[i][j]]+=a[i][j];
        maxv=max(maxv,a[i][j]);
    }
    if(num[1]!=num[2]) 
    {
    maxv=(sum[1]-sum[2])/(num[1]-num[2]);
    work(maxv);
    ll ans=dinic();
    if(ans+sum[2]==num[2]*maxv)
    cout<<ans<<endl;
    else printf("-1\n");
    }
    else
    {
        if(sum[2]!=sum[1]){
            printf("-1\n");
            return;
        }
        int A=maxv;
        int B=maxv+100;
        ll ans2=-1;
        while(A<=B)
        {
            int C=(A+B)>>1;
            work(C);
            ll ans=dinic();
            if(ans+sum[2]==num[2]*C) ans2=ans,B=C-1;
            else A=C+1;
        }
        cout<<ans2<<endl;
    }
}
int main()
{
    //freopen("game.in","r",stdin);
    //freopen("game.out","w",stdout);
    int T;
    scanf("%d",&T);
    while(T--)
    {
        init();
    }
    return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值