BZOJ 2756 SCOI2012 奇怪的游戏

26 篇文章 0 订阅
11 篇文章 0 订阅

2756: [SCOI2012]奇怪的游戏

Time Limit: 40 Sec Memory Limit: 128 MB

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

分析:
首先我们考虑对棋盘黑白染色,那么我们发现:“每次相邻两个+1”,显然是一黑一白 +1
那么我们先统计出WhiteNum,BlackNum(黑白点的数目),WhiteSum,BlackSum(黑白点初始权值和)
那么对于一次增加,显然是WhiteSum+1,BlackSum+1
考虑对最后的情况进行讨论:
那么很显然,当WhiteNum==BlackNum时(即总点数为偶数)
如果WhiteSum!=BlackSum,显然无解
如果WhiteSum==BlackSum时,我们发现,对于X如果成立,那么X+1一定成立,显然满足二分的性质,那么二分这个值,进行判定
当WhiteNum!=BlackNum时(即总点数为奇数)
发现显然,若有解,则解唯一,那么直接验证正确性即可
至于解怎么求?
那么假设我们知道最后值为X,那么显然可以得到X∗WhiteNum−WhiteSum=X∗BlackNum−BlackSum
移项后显然可以化减出,eeeee,公式有点难写,自己化去吧

那么考虑建图:
S–>白点,约束为X-val[i][j]
黑点–>T,约束为X-val[i][j]
相邻的白点–>黑点,约束为INF
判断是否满流即可

#include<iostream>
#include<cstring>
#include<queue>
#include<cstdio>
#include<cmath>
#include<algorithm>
using namespace std;
inline int read(int &x){
    x=0;int f=1;char c=getchar();
    while(c>'9'||c<'0'){ if(c=='-') f=-1; c=getchar(); }
    while(c>='0'&&c<='9'){ x=(x<<1)+(x<<3)+(c-'0'); c=getchar(); } x*=f;
}
#define MAXM 1001000
#define LL long long
#define MAXN 2010
int Cas,m,n,mat[50][50];
struct Edge{ int to,next;LL val; }e[MAXM];
int head[MAXN],tot=1;
inline void Add_Edge(int u,int v,LL w){
    e[++tot].to=v;e[tot].val=w;
    e[tot].next=head[u];head[u]=tot;
}
inline void Insert_Edge(int u,int v,LL w){ Add_Edge(u,v,w);Add_Edge(v,u,0); }
int dep[MAXN],cur[MAXN],S,T;
queue<int> q;
bool BFS(){
    while(!q.empty()) q.pop();
    memset(dep,-1,sizeof dep );
    q.push(S);dep[S]=0;
    while(!q.empty()){
        int u=q.front();q.pop();
        for(int i=head[u];i;i=e[i].next){
            int v=e[i].to;
            if(e[i].val&&dep[v]==-1){ dep[v]=dep[u]+1;q.push(v); }
        }
    }
    return dep[T]!=-1;
}
#define INF 1LL<<60
inline bool ok(int x,int y){ return (x>=1&&x<=n&&y>=1&&y<=m); }
inline void init(){ tot=1;memset(head,0,sizeof head ); }
int col[50][50],id[50][50];
LL Sum_b,Sum_w,Num_w,Num_b,ID;
LL DFS(int u,LL flow){
    if(u==T) return flow;
    LL ret=0;
    for(int i=cur[u];i;i=e[i].next){
        int v=e[i].to;
        if(e[i].val&&dep[v]==dep[u]+1){
            LL w=DFS(v,min(flow-ret,e[i].val));
            e[i].val-=w;e[i^1].val+=w;ret+=w;
            if(e[i].val) cur[u]=i;
            if(ret==flow) return ret;
        }
    }
    if(!ret) dep[u]=-1;
    return ret;
}
LL Dinic(){
    LL ret=0;
    while(BFS()){
        for(int i=S;i<=T;i++) cur[i]=head[i];
        ret+=DFS(S,INF);
    }
    return ret; 
}
bool Check(LL x){
    LL Tot=0; init();
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++){
            if(col[i][j]){
                Insert_Edge(S,id[i][j],x-mat[i][j]);Tot+=(x-mat[i][j]);
                if(ok(i-1,j)) Insert_Edge(id[i][j],id[i-1][j],INF);
                if(ok(i,j-1)) Insert_Edge(id[i][j],id[i][j-1],INF);
                if(ok(i,j+1)) Insert_Edge(id[i][j],id[i][j+1],INF);
                if(ok(i+1,j)) Insert_Edge(id[i][j],id[i+1][j],INF);
            }
            else Insert_Edge(id[i][j],T,x-mat[i][j]);
        }
    LL ans=Dinic();
    return Tot==ans;
}
void Build(){
    S=0; T=n*m+1; Sum_b = Sum_w = Num_b = Num_w = 0; ID=0; int maxx=0;
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++)
            col[i][j]=(i+j)&1,id[i][j]= ++ID,maxx=max(maxx,mat[i][j]);
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++){
            if(col[i][j]) Sum_w += mat[i][j],Num_w++;
            else Sum_b += mat[i][j],Num_b++;
        }
    if(Num_w==Num_b&&Sum_b!=Sum_w){ puts("-1");return; }
    if(Num_w==Num_b){
        LL l=maxx,r=(1LL<<50);
        while(l<=r){
            LL Mid=(l+r)>>1;
            if(Check(Mid)) r=Mid-1;
            else l=Mid+1;
        }
        printf("%lld\n",l*Num_w-Sum_w);
    } else {
        LL x=(Sum_b-Sum_w)/(Num_b-Num_w);
        if(x<maxx) {puts("-1");return;}
        if(Check(x)) printf("%lld\n",x*Num_w-Sum_w);
        else puts("-1");
    }
}
int main(){
    read(Cas);
    while(Cas--){
        read(n); read(m);
        for(int i=1;i<=n;i++)
            for(int j=1;j<=m;j++) read(mat[i][j]);
        Build();
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

七情六欲·

学生党不容易~

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值