SCOI2012——奇怪的游戏(最大流+二分检验)

2756: [SCOI2012]奇怪的游戏

Time Limit: 40 Sec  Memory Limit: 128 MB
Submit: 490  Solved: 114
[Submit][Status][Discuss]
Description

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

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

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

Sample Input

2 2 
1 2 
2 3 
3 3 
1 2 3 
2 3 4 
4 3 2 
Sample Output


-1 
HINT

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

Source

day2

解析:

         将所有格子进行黑白染色。。。因为每次对相邻的两个格子加1,所以相邻的两个格子肯定属于二分图的两边。。。

         然后一边的格子连源点,容量为我们二分出的所有格子最后变成的值x-map[i][j],另一边同样连汇点。。。中间相邻的格子连容量无穷的边。。。

         以上是网络流建图部分。。。

         首先,如果有奇数个格子。。。计算出黑白格子总和的差值diff,如果diff<maxw输-1

         否者,跑最大流,如果能跑满,即flow(diff)==(diff*n*m-sum)>>1;输出等式右边部分,否者-1.。。

         如果有偶数个格子。。。

         我们可以证明当所有格子最后变成一个数成立,那么大于这个数的所有情况都成立。。。

         于是很容易想到二分,分到一个值,如果满足就在左区间找否者就在右区间找。。。

         注意本题会超int

         ps:据说本题卡递归的sap,其实不是。。。我就是用递归sap,但需要小优化一下。。。要把sap的 if(d[0]>=l+2)return res;这句话放到循环里面

                 这儿太坑了,整整Debug了两天。。。

代码:

/**************************************************************
    Problem: 2756
    User: jianing
    Language: C++
    Result: Accepted
    Time:11884 ms
    Memory:3192 kb
****************************************************************/
 
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<cstring>
using namespace std;
typedef long long LL;
const LL inf=0x3f3f3f3f3f3f3f3fll;
#define most 1000000000
 
int n,m,l=0,e=0;
int map[50][50],val[2510];
struct node
{
    int u,v,next;
    LL c;
}edge[100000];
int head[100000];
int d[2500],sumd[2500];
int maxx;
LL sum1,sum2;
 
void read()
{
    freopen("game.in","r",stdin);
    freopen("game.out","w",stdout);
}
 
void add(int u,int v,LL c)
{
    edge[e].u=u;
    edge[e].v=v;
    edge[e].c=c;
    edge[e].next=head[u];
    head[u]=e++;
    edge[e].u=v;
    edge[e].v=u;
    edge[e].c=0;
    edge[e].next=head[v];
    head[v]=e++;
}
 
LL sap(int u,LL flow)
{
    if(u==l+1)return flow;
    LL res=0;
    for(int i=head[u];i!=-1;i=edge[i].next)
    {
        int v=edge[i].v;
        if(edge[i].c && d[u]==d[v]+1)
        {
            LL t=sap(v,(LL)min(edge[i].c,(flow-res)));
            res+=t;
            edge[i].c-=t;edge[i^1].c+=t;
            if(res==flow)return res;
            if(d[0]>=l+2)return res;//本来放在下面,T哭了
        }                           //
    }                               //
    sumd[d[u]]--;                   //就这儿
    if(sumd[d[u]]==0)d[0]=l+2;
    sumd[++d[u]]++;
    return res;
}
 
LL flow(LL x)
{
    memset(head,-1,sizeof(head));
    memset(edge,0,sizeof(edge));
    e=0;l=n*m;int t=n*m+1;
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++)
        {
            if((i+j)&1)
            {
                add(0,m*(i-1)+j,x-map[i][j]);
                if(i>1)add(m*(i-1)+j,m*(i-2)+j,inf);
                if(i<n)add(m*(i-1)+j,m*i+j,inf);
                if(j>1)add(m*(i-1)+j,m*(i-1)+j-1,inf);
                if(j<m)add(m*(i-1)+j,m*(i-1)+j+1,inf);
            }
            else add(m*(i-1)+j,t,x-map[i][j]);
        }
    memset(d,0,sizeof(d));
    memset(sumd,0,sizeof(sumd));
    sumd[0]=l+2;
    LL ans=0;
    while(d[0]<=l+1)
        ans+=sap(0,inf);
    return ans;
}
 
 
void check()
{
    LL best=inf;
    LL ll=(LL)maxx,rr=inf;
    while(ll<rr)
    {
        LL mid=(ll+rr)>>1;
        LL ans=flow(mid);
        LL tmp=mid*n*m-sum1-sum2;
        if(tmp==ans*2)
        {
            if(ans>0)best=min(best,ans);
            rr=mid;
        }
        else ll=mid+1;
    }
    printf("%lld\n",best);
}
 
LL max(LL a,LL b)
{
    return a>b?a:b;
}
 
void work()
{
    e=0;maxx=0;
    sum1=0;sum2=0;int cnt1=0,cnt2=0;
    memset(map,0,sizeof(map));
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++)
        {
            scanf("%d",&map[i][j]);
            maxx=max(maxx,map[i][j]);
            if((i+j)&1)
            {
                sum1+=map[i][j];
                cnt1++;
            }
            else
            {
                sum2+=map[i][j];
                cnt2++;
            }
        }
    if(cnt1!=cnt2)
    {
        if((sum1-sum2)%(cnt1-cnt2)!=0)printf("-1\n");
        LL diff=(sum1-sum2)/(cnt1-cnt2);
        if(diff<maxx)
        {
            printf("-1\n");
            return ;
        }
        else
        {
            LL ans=flow(diff);
            LL tmp=diff*n*m-sum1-sum2;
            if(tmp==ans*2)printf("%lld\n",ans);
            else printf("-1\n");
        }
    }
    else check();
}
 
int main()
{
   // read();
    int test;
    scanf("%d",&test);
    while(test--)
    work();
    return 0;
}




  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值