bzoj2756 [SCOI2012]奇怪的游戏

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

这道题也是一道挺好的题,参考了黄学长的blog:http://hzwer.com/5992.html

那我在这里把他的题解再讲一下..

首先我们把这整个n*m的图黑白染色,那么每一次的操作就相当于给一个黑点和一个白点加一

然后我们用 sum1 表示白点的总和, num1 表示白点的个数, sum2 表示黑点的总和, num2 表示黑点的个数。假设最后的结果是所有的数都为 x ,那么:
num1xsum1=num2xsum2

化简得:

x=sum1sum2num1num2

我们就可以很轻易的得到了我们要的答案 x ,但这个x不一定能够满足条件(自己想想为什么),这时候就要用到网络流

假如给一个白点加1,那么它相邻的4个黑点肯定有其中一个要加1。根据流量平衡,我们可以这样构图:源连白点,黑点连汇,流量为当前点要达到 x 需要的费用,白点连相邻的黑点,流量为INF,表示我可以对这一对点无限的同时加1

那么现在问题回到计算x的公式上,能成立的只是当 num1num2 ,假如 nm mod 2=0 ,那么 num1=num2 ,这个公式就不适用了

这时候我们再想一下,如果一个 x 满足条件,那么对于一个x(x),肯定也满足条件。为什么呢?自己脑补一个像这样的矩形,肯定存在一个方案使得整个矩形都加1

那么也就是说, x <script type="math/tex" id="MathJax-Element-3404">x</script>是满足二分的.. 所以我们就可以用二分解决..

#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <algorithm>
#include <queue>
#define LL long long
using namespace std;
const LL Maxn = 1610;
const LL inf = (LL)1<<50;
const LL dx[4] = { 0, 1, 0, -1 };
const LL dy[4] = { 1, 0, -1, 0 };
struct node {
    LL x, y, next, c, opp;
}a[Maxn*10]; LL first[Maxn], len;
LL _min ( LL x, LL y ){ return x < y ? x : y; }
LL _max ( LL x, LL y ){ return x > y ? x : y; }
void ins ( LL x, LL y, LL c ){
    len ++; LL k1 = len;
    a[len].x = x; a[len].y = y; a[len].c = c;
    a[len].next = first[x]; first[x] = len;
    len ++; LL k2 = len;
    a[len].x = y; a[len].y = x; a[len].c = 0;
    a[len].next = first[y]; first[y] = len;
    a[k1].opp = k2;
    a[k2].opp = k1;
}
LL st, ed, h[Maxn];
LL n, m;
LL na[45][45];
LL getnum ( LL x, LL y ){ return (x-1)*m+y; }
bool bfs (){
    queue <LL> q;
    memset ( h, -1, sizeof (h) ); 
    q.push (st); h[st] = 0;
    while ( !q.empty () ){
        LL x = q.front (); q.pop ();
        for ( LL k = first[x]; k; k = a[k].next ){
            LL y = a[k].y;
            if ( h[y] == -1 && a[k].c > 0 ){
                h[y] = h[x]+1;
                q.push (y);
            }
        }
    }
    return h[ed] > 0;
}
LL dfs ( LL x, LL flow ){
    if ( x == ed ) return flow;
    LL delta = 0;
    for ( LL k = first[x]; k; k = a[k].next ){
        LL y = a[k].y;
        if ( h[y] == h[x]+1 && a[k].c > 0 && flow-delta > 0 ){
            LL minf = dfs ( y, _min ( a[k].c, flow-delta ) );
            delta += minf;
            a[k].c -= minf;
            a[a[k].opp].c += minf;
        }
    }
    if ( delta == 0 ) h[x] = -1;
    return delta;
}
bool check ( LL x ){
    LL i, j, k;
    LL sum = 0;
    st = 0; ed = n*m+1;
    len = 0; memset ( first, 0, sizeof (first) );
    for ( i = 1; i <= n; i ++ ){
        for ( j = 1; j <= m; j ++ ){
            if ( (i+j) % 2 == 1 ){
                ins ( st, getnum (i,j), x-na[i][j] );
                sum += x-na[i][j];
                for ( k = 0; k < 4; k ++ ){
                    LL ii = i+dx[k], jj = j+dy[k];
                    if ( ii < 1 || ii > n || jj < 1 || jj > m ) continue;
                    ins ( getnum (i,j), getnum (ii,jj), inf );
                }
            }
            else ins ( getnum (i,j), ed, x-na[i][j] );
        }
    }
    LL ans = 0, delta;
    while ( bfs () ){
        while ( delta = dfs ( st, inf ) ) ans += delta;
    }
    if ( ans == sum ) return true;
    else return false;
}
int main (){
    LL i, j, k, T;
    scanf ( "%lld", &T );
    while ( T -- ){
        scanf ( "%lld%lld", &n, &m );
        LL sum1, sum2, num1, num2;
        sum1 = sum2 = num1 = num2 = 0;
        LL mx = 0;
        for ( i = 1; i <= n; i ++ ){
            for ( j = 1; j <= m; j ++ ){
                scanf ( "%lld", &na[i][j] );
                if ( (i+j) % 2 == 1 ){ sum1 += na[i][j]; num1 ++; }
                else { sum2 += na[i][j]; num2 ++; }
                mx = _max ( mx, na[i][j] );
            }
        }
        if ( num1 != num2 ){
            LL x = ( sum1-sum2 ) / (num1-num2);
            if ( x >= mx ){
                if ( check (x) == true ){
                    printf ( "%lld\n", x*num1-sum1 );
                    continue;
                }
            }
            printf ( "-1\n" );
        }
        else {
            LL l = mx, r = inf, ret;
            while ( l <= r ){
                LL mid = ( l + r ) >> 1;
                if ( check (mid) == true ){ ret = mid; r = mid-1; }
                else l = mid+1;
            }
            printf ( "%lld\n", ret*num1-sum1 );
        }
    }
    return 0;
}

几个需要注意的地方:
1.由于数字都很大,请用long long
2.二分左端点应该从矩阵中最大的数开始.. 否则会错哒..
3.记住要用lld啊.. 我因为这个错了一次..
4.尽量把二分的右端点往大的移.. 因为这个答案.. 说不定的嘛..

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值