【SCOI2012】【二分法】【最大流】奇怪的游戏

来源:互联网 发布:婚礼相册制作软件 编辑:程序博客网 时间:2024/05/14 00:22

这道题初看也许会感觉无法下手,由于每次操作都是相邻的两个,所以可以考虑将棋盘黑白染色,这样我们可以对黑色的格子和白色的格子单独考虑。

设黑色格子个数为cnt1,总和为sum1,白色格子个数为cnt2,总和为sum2,最终所有格子都变成了x,则很容易写出下列的关系式:

     cnt1 * x - sum1 = cnt2 * x - sum2

=> x * (cnt1 - cnt2) = sum1 - sum2

=> x = (sum1 - sum2) / (cnt1 - cnt2)

现在讨论cnt1 与 cnt2的情况

1、若cnt1 = cnt2,则说明在sum1 = sum2的情况下若x可行,则x+1同样可以,这样我们可以通过二分的方法找出最小的满足条件的x,求出最小步数;若sum1 != sum2则无解。

2、若cnt1 != cnt2,则我们解出了一个x

这个x必须要满足三个条件

(1)x必须为整数

(2)x不小于所有格子的最大值

(3)x必须要通过检验

如果x满足这三个条件,则我们可以解出最小步数,否则无解。

那么我们怎样检验x是否可行呢,这个需要通过网络流来验证。

设map[i][j]为格子对应的数字,对于每个黑色的格子,从源点一条边,容量为x - map[i][j],并且对它周围的四个白色格子连一条边容量为inf;对于每个白色格子,向汇点连一条边,容量为x - map[i][j]

设最大流为maxflow,若maxflow = (n * m * x - sum1 - sum2) >> 1的话,说明存在方案可以使所有数变成x,同时最小步数minstep = (n * m * x - sum1 - sum2) >> 1

注意到格子的数可能很大,所以要使用long long类型

这样就解决了本题

代码

#include<cstdio>#include<cstring>using namespace std;const int maxn = 50;const int maxpoint = 3000;const int maxm = 100000;const long long inf = 0x3f3f3f3f3f3f3f3fll;struct Edge{int pos;long long c;int next;}E[maxm];int map[maxn][maxn],head[maxpoint];int dis[maxpoint],gap[maxpoint],cur[maxpoint],pre[maxpoint];int T,n,m;int NE,s,t,nodenum;long long maxw,cnt1,sum1,cnt2,sum2,com;void init(){freopen("bzoj2756.in","r",stdin);freopen("bzoj2756.out","w",stdout);}inline int max(long long a,long long b){return a > b ? a : b;}inline void checkmin(long long &a,long long b){if(a == -1 || a > b)a = b;}inline long long gettime(long long x){return (x * n * m - sum1 - sum2) >> 1;}void insert(int u,int v,long long c){E[NE].c = c;E[NE].pos = v;E[NE].next = head[u];head[u] = NE++;E[NE].c = 0;E[NE].pos = u;E[NE].next = head[v];head[v] = NE++;}long long sap(){memset(dis,0,sizeof(dis));memset(gap,0,sizeof(gap));for(int i = s;i <= t;i++)cur[i] = head[i];int u = pre[s] = s;long long  maxflow = 0,aug = -1;gap[0] = nodenum;while(dis[s] < nodenum){loop:  for(int &i = cur[u];i != -1;i = E[i].next)   {   int v = E[i].pos;   if(E[i].c && dis[u] == dis[v] + 1)   {   checkmin(aug,E[i].c);   pre[v] = u;   u = v;   if(v == t)   {   maxflow += aug;   for(u = pre[u];v != s;v = u,u = pre[u])   {       E[cur[u]].c -= aug;   E[cur[u]^1].c += aug;   }   aug = -1;   }   goto loop;   }   }   int mind = nodenum;   for(int i = head[u];i != -1;i = E[i].next)   {   int v = E[i].pos;   if(E[i].c && (mind > dis[v]))   {cur[u] = i;mind = dis[v];   }   }   if(--(gap[dis[u]]) == 0)break;   gap[dis[u] = mind + 1]++;   u = pre[u];}return maxflow;}bool check(long long x){memset(E,0,sizeof(E));memset(head,-1,sizeof(head));NE = 0,s = 0,t = n * m + 1;nodenum = t + 1;for(int i = 1;i <= n;i++){for(int j = 1;j <= m;j++){if((i + j) & 1){insert(s,(i - 1) * m + j,x - map[i][j]);if(i > 1)insert((i - 1) * m + j,(i - 2) * m + j,inf);if(i < n)insert((i - 1) * m + j,i * m + j,inf);if(j > 1)insert((i - 1) * m + j,(i - 1) * m + j - 1,inf);if(j < m)insert((i - 1) * m + j,(i - 1) * m + j + 1,inf);}else insert((i - 1) * m + j,t,x - map[i][j]);}}return ((sap() << 1) == (x * n * m - sum1 - sum2));}void solve(){if(cnt1 != cnt2){if((sum1 - sum2) % (cnt1 - cnt2) != 0)printf("-1\n");else{long long x = (sum1 - sum2) / (cnt1 - cnt2);if(x < maxw){printf("-1\n");return;}if(check(x))printf("%lld\n",gettime(x));else printf("-1\n");}}else{long long l = maxw,r = inf;while(l < r){long long m = (l + r) >> 1;if(check(m))r = m;else l = m + 1;}if(check(r))printf("%lld\n",gettime(r));else printf("-1\n");}}void readdata(){scanf("%d",&T);while(T--){memset(map,0,sizeof(map));maxw = 0,sum1 = 0,cnt1 = 0,sum2 = 0,cnt2 = 0;scanf("%d%d",&n,&m);for(int i = 1;i <= n;i++){for(int j = 1;j <= m;j++){scanf("%d",&map[i][j]);if((i + j) & 1)cnt1++,sum1 += map[i][j];else cnt2++,sum2 += map[i][j];maxw = max(maxw,map[i][j]);}}solve();}}int main(){init();readdata();return 0;}