[SCOI2012]奇怪的游戏

2756: [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

Solution

话说这个题调了前后加起来一共快2h..

其实也不是特别的难了。

首先我们以发现的是,这个题的那个加的操作只能改变一个格子和它上下左右相邻的一个格子,也就是一个黑白染色的模型。

然后对小数据分情况讨论可以发现,格子数是奇数的情况和格子数是偶数的情况并不一样。然后我们可以对这两种情况分情况讨论。

1.格子数为偶数

可以发现的是这种情况矩阵中的黑格子和白格子的数目相等,那么我们不妨分别计算一下这两种不同颜色的格子里数字权值的总和,并设它们为S1和S2。明显的一点是,如果\(S1\neq S2\),那么我们这个时候就要输出-1。如果\(S1=S2\),那么就可以二分我们最小需要到达的值,然后跑网络流计算流量就可以了。

2.格子数为奇数

可以发现的是这个时候我们只有唯一的一种解法,那么我们如何直接快速的出来呢,我们可以通过对数据进行分析,假设我们以最开始的格子为白格子,那么白格子就会比黑格子多一个。那么我们只需要保证这么一个式子:\(x\times c1-S1=x\times c2-S2\)就可以了。那么我们移项之后会发现,\(x=\frac{S1-S2}{c1-c2}\),所以我们就可以直接判断这个x能否满足要求即可。

Code

#pragma comment(linker, "/STACK:1024000000,1024000000")
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<queue>
#define re register
#define inf 100000000000000ll
#define MAXN 2001
#define MAXM 100001
#define ll long long
using namespace std;
ll n,s,t;
ll a[42][42],out,T,id[42][42],nm,m,maxx;
ll S1,S2;
struct po
{
    int nxt,to;
    ll w;
}edge[MAXM];
int head[MAXN],dep[MAXN],num=-1,cur[200051];
inline int read()
{
    int x=0,c=1;
    char ch=' ';
    while((ch>'9'||ch<'0')&&ch!='-')ch=getchar();
    while(ch=='-')c*=-1,ch=getchar();
    while(ch<='9'&&ch>='0')x=x*10+ch-'0',ch=getchar();
    return x*c;
}
inline void add_edge(int from,int to,ll w)
{
    edge[++num].nxt=head[from];
    edge[num].to=to;
    edge[num].w=w;
    head[from]=num;
}
inline void add(int from,int to,ll w)
{
    add_edge(from,to,w);
    add_edge(to,from,0);
}
inline bool bfs()
{
    memset(dep,0,sizeof(dep));
    queue<int> q;
    while(!q.empty())
    q.pop();
    q.push(s);
    dep[s]=1;
    while(!q.empty())
    {
        int u=q.front();
        q.pop();
        for(re int i=head[u];i!=-1;i=edge[i].nxt)
        {
            int v=edge[i].to;
            if(dep[v]==0&&edge[i].w>0)
            {
                dep[v]=dep[u]+1;
                if(v==t)
                return 1;
                q.push(v);
            }
        }
    }
    return 0;
}
inline ll dfs(int u,ll dis)
{
    if(u==t)
    return dis;
    ll diss=0;
    for(re int& i=cur[u];i!=-1;i=edge[i].nxt)
    {
        ll v=edge[i].to;
        if(edge[i].w>0&&dep[v]==dep[u]+1)
        {
            ll check=dfs(v,min(dis,(ll)edge[i].w));
            if(check>0)
            {
                dis-=check;
                diss+=check;
                edge[i].w-=check;
                edge[i^1].w+=check;
                if(dis==0) break;
            }
        }
    }
    return diss;
}
inline ll dinic()
{
    ll ans=0;
    while(bfs())
    {
        for(re int i=0;i<=t;i++)
        cur[i]=head[i];
        while(ll d=dfs(s,inf))
        ans+=d;
    }
    return ans;
}
inline void prepare()
{
    memset(head,-1,sizeof(head));
    num=-1;
}
inline bool check(ll x)
{
    ll tot=0;
    prepare();
    for(re int i=1;i<=n;i++)
        for(re int j=1;j<=m;j++)
            if(!((i+j)&1)){
                tot+=x-a[i][j];
                add(s,id[i][j],x-a[i][j]);
                if(id[i][j]%m!=1) add(id[i][j],id[i][j]-1,inf);
                if(id[i][j]%m!=0) add(id[i][j],id[i][j]+1,inf);
                if(id[i][j]>m) add(id[i][j],id[i][j]-m,inf);
                if(id[i][j]<=(n-1)*m) add(id[i][j],id[i][j]+m,inf);
            }else add(id[i][j],t,x-a[i][j]);
        ll whatever=dinic();
    return tot==whatever;
}
int main() 
{
    T=read();
    while(T--){
        n=read();m=read();
        s=0;t=n*m+1;S1=0;S2=0;maxx=0;
        for(re int i=1;i<=n;i++)
            for(re int j=1;j<=m;j++){
                a[i][j]=read();
                id[i][j]=(i-1)*m+j;
                maxx=max(a[i][j],maxx);
            }
            for(re int i=1;i<=n;i++)
                for(re int j=1;j<=m;j++)
                    if(!((i+j)&1)) S1+=a[i][j];
                    else S2+=a[i][j];
        if(n*m%2==1){
            ll wish=(S1-S2)/(1);
            if(wish>=m&&check(wish)) cout<<wish*((n*m>>1)+1)-S1<<endl;
            else cout<<"-1"<<endl;
        } else {
            if(S1!=S2){
                cout<<"-1"<<endl;
                continue;
             }
            ll l=maxx,r=2000000001;
            while(l<=r){
                ll mid=(l+r)>>1;
                if(check(mid)) r=mid-1;
                else l=mid+1;
            }
            cout<<l*(n*m>>1)-S1<<endl;
        }
    }
    return 0;
}

转载于:https://www.cnblogs.com/victorique/p/8868027.html

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值