zoukankan      html  css  js  c++  java
  • 【ybt金牌导航3-2-4】【luogu P5038】奇怪游戏 / 奇怪的游戏

    奇怪游戏 / 奇怪的游戏

    题目链接:ybt金牌导航3-2-4 / luogu P5038

    题目大意

    有一个棋盘,每次你可以选相邻的两个位置都加一。
    问你最少要多少次操作才能让棋盘上的数都变成一样的,如果不能就输出 -1。

    思路

    看到相邻的位置想到把图黑白染色。
    然后要把数变成一样你就可以先到用网络流来搞,判断它是否流满的方法来看是否变成一样。
    但它要的是最少次的操作,网络流是求最大流,自然想到用二分。

    然后开始搞具体实现。
    首先你二分的肯定是最后变成的数 (X),那黑色点的个数是 (num_0),权值和是 (sum_0),白色的个数是 (num_1),权值和是 (sum_1),那我们就是要找一个最小的 (X),使得这个式子被满足:
    (num_0*X-sum_0=num_1*X-sum_1)(因为你操作一次就相当于给 (sum_0,sum_1) 都加一)

    然后化一下式子,得到:(X=dfrac{sum_0-sum_1}{num_0-num_1})
    那如果 (num_0=num_1),那除数就变成了 (0),那就出问题了,这个时候是什么鬼呢?

    我们考虑从 (x*y=z),得到 (x=frac{z}{y}),那当 (y=0,z=0) 时,(x) 可以是任意的数,因为任意的数乘 (0) 都是 (0)
    那也就是说,当 (sum_0=sum_1)(num_0=num_1) 时,就会有多组解,那这个时候我们就要上二分,通过用网络流来验证这个答案行不行,然后找到最小的那个。
    那如果 (y=0,z eq0),就是 (x) 什么实数都不行,因为没有实数乘 (0) 会变成非零数。那也就说当 (num_0=num_1)(sum_0 eq sum_1) 的时候,就是无解的,直接输出 (-1)

    那如果 (num_0 eq num_1),那你会发现,这个 (X) 的解是唯一的,而且你还能把它算出来。
    但是你算出来之后你还要用网络流验算一下,如果不是还是要输出 (-1)

    那接着新的问题又来了,如果构网络流的图(是跑最大流应该很显然了吧)。
    那我们考虑这样,假设你二分的是 (X),点 ((i,j)) 权值为 (a_{i,j})
    源点连线黑色点 ((x_1,y_1)),流量是 (X-a_{x_1,y_1})
    表示这个黑点最多能被加多少次。
    那现在只有黑色点能被加,我们就把黑色点连向它旁边的白色点,流量无限。
    这样就实现了黑点和白点捆绑加,你加黑点就一定要把其中一个旁边的白点也加了,不然不能流到白点。
    那白点又能被加多少次呢,白色 ((x_2,y_2)) 连汇点,流量是 (X-a_{x_2,y_2})。(流量就是它能被加的次数)

    那如果判断这个方案是否可行呢?
    我们可以跑最大流,然后看是否已经流满,就看最大流是否等于所有 (X-a_{x_1,y_1}) 的和。
    (当然你用 (X-a_{x_2,y_2}) 也可以,反正你求出来的公式就已经让它们是相同的)

    然后搞就可以了。

    代码

    #include<queue>
    #include<cstdio>
    #include<cstring>
    #include<iostream>
    #define ll long long
    #define INF 0x3f3f3f3f3f3f3f3f
    
    using namespace std;
    
    struct node {
    	ll x, to, nxt, op;
    }e[500001];
    ll TI, n, m, le[5001], lee[5001], S, T;
    ll a[41][41], l, r, KK, dis[5001], tot_num;
    ll dx[4] = {1, 0, -1, 0}, dy[4] = {0, 1, 0, -1};
    ll sum[2], num[2];
    queue <int> q;
    
    void csh() {
    	l = 0;
    	r = INF;
    	sum[0] = sum[1] = 0;
    	num[0] = num[1] = 0;
    }
    
    void csh_wll() {
    	memset(le, 0, sizeof(le));
    	KK = 0;
    	tot_num = 0; 
    }
    
    bool ck(ll x, ll y) {
    	if (x < 1 || x > n) return 0;
    	if (y < 1 || y > m) return 0;
    	return 1;
    }
    
    void add(ll x, ll y, ll z) {
    	e[++KK] = (node){z, y, le[x], KK + 1}; le[x] = KK;
    	e[++KK] = (node){0, x, le[y], KK - 1}; le[y] = KK;
    }
    
    bool bfs() {
    	for (ll i = 1; i <= tot_num; i++) {
    		dis[i] = -1;
    		lee[i] = le[i];
    	}
    	while (!q.empty()) q.pop();
    	
    	q.push(S);
    	dis[S] = 0;
    	while (!q.empty()) {
    		ll now = q.front();
    		q.pop();
    		
    		for (ll i = le[now]; i; i = e[i].nxt)
    			if (e[i].x > 0 && dis[e[i].to] == -1) {
    				dis[e[i].to] = dis[now] + 1;
    				if (e[i].to == T) return 1;
    				q.push(e[i].to);
    			}
    	}
    	
    	return 0;
    }
    
    ll dfs(ll now, ll sum) {
    	if (now == T) return sum;
    	
    	ll go = 0;
    	for (ll &i = lee[now]; i; i = e[i].nxt)
    		if (e[i].x > 0 && dis[e[i].to] == dis[now] + 1) {
    			ll this_go = dfs(e[i].to, min(sum - go, e[i].x));
    			if (this_go) {
    				e[i].x -= this_go;
    				e[e[i].op].x += this_go;
    				go += this_go;
    				if (go == sum) return go;
    			}
    		}
    	
    	if (go < sum) dis[now] = -1;
    	return go;
    }
    
    ll dinic() {
    	ll re = 0;
    	while (bfs())
    		re += dfs(S, INF);
    	return re;
    }
    
    bool check(ll now) {
    	csh_wll();
    	
    	ll needsum = 0;
    	S = n * m + 1;
    	T = n * m + 2;
    	tot_num = T; 
    	for (ll i = 1; i <= n; i++)
    		for (ll j = 1; j <= m; j++) {
    			if ((i + j) & 1) {
    				add(S, (i - 1) * m + j, now - a[i][j]);//起点->黑点
    				for (ll k = 0; k < 4; k++)
    					if (ck(i + dx[k], j + dy[k]))
    						add((i - 1) * m + j, (i + dx[k] - 1) * m + j + dy[k], INF);//黑点->它旁边的白点
    			}
    			else add((i - 1) * m + j, T, now - a[i][j]), needsum += now - a[i][j];//白点->终点
    		}
    	
    	return dinic() == needsum;//判断是否流满,流满就代表可以
    }
    
    int main() {
    	scanf("%lld", &TI);
    	while (TI--) {
    		csh();
    		
    		scanf("%lld %lld", &n, &m);
    		
    		for (ll i = 1; i <= n; i++)
    			for (ll j = 1; j <= m; j++) {
    				scanf("%lld", &a[i][j]);
    				l = max(l, a[i][j]);
    				sum[(i + j) & 1] += a[i][j];
    				num[(i + j) & 1]++;
    			}
    		
    		if (num[0] == num[1]) {
    			if (sum[0] != sum[1]) {//根据公式可以看出无解
    				printf("-1
    ");
    				continue;
    			}
    			
    			//否则有很多解,要通过二分找到次数最少的,也就是最后加到的值最小的
    			ll ans = -1, mid;
    			while (l <= r) {
    				mid = (l + r) >> 1;
    				if (check(mid)) {
    					ans = mid;
    					r = mid - 1;
    				}
    				else l = mid + 1;
    			}
    			
    			if (ans == -1) {
    				printf("-1
    ");
    				continue;
    			}
    			else printf("%lld
    ", (n * m * ans - sum[0] - sum[1]) / 2);
    		}
    		else {
    			ll X = (sum[0] - sum[1]) / (num[0] - num[1]);//直接算出答案
    			if (X >= l && check(X)) {//要验证
    				printf("%lld
    ", (n * m * X - sum[0] - sum[1]) / 2);
    			}
    			else printf("-1
    ");
    		}
    	}
    	
    	return 0;
    }
    
    
  • 相关阅读:
    解释JUNIT中@BEFORECLASS和@AFTERCLASS标注的方法必须是STATIC的,而在TESTNG不必
    XXL开源社区
    java中的IO整理
    Spring MVC 原理探秘
    Servlet一次乱码排查后的总结
    正则表达式简明参考
    牛皮博客
    【转】线程安全的单例模式
    springboot下载excel(解决文件损坏问题)
    JZOJ-TGB817-SOL
  • 原文地址:https://www.cnblogs.com/Sakura-TJH/p/YBT_JPDH_3-2-4.html
Copyright © 2011-2022 走看看