[bzoj2597][Wc2007]剪刀石头布_费用流

[Wc2007]剪刀石头布

题目大意https://www.lydsy.com/JudgeOnline/problem.php?id=2597


题解

发现直接求三元环不好求,我们考虑任选三个点不是三元环的个数。

这样的话,必定是有一个点被其余两个点指,我们就根据这个来求。

又发现,最后的答案之和所有点的度数有关。

就是,$\sum C_{d_i}^{2}$。

紧接着,因为度数和是一定的。而且已经有了一些边。

现在就是有固定的度数可以分配,每个点有一个分配上限,怎么分配最少?

发现一个事,就是$C_{d_i + 1}^{2} - C_{d_i} ^ {2} < C_{d_i + 2}^{2} - C_{d_i + 1}^{2}$。

根据这个性质,我们就可以暴力连边费用流了。

我们就把权值的差当做费用,它肯定会从低往高走因为是最小费用,满足题意。

代码

#include <bits/stdc++.h>

#define N 11010 

#define M 500010 

using namespace std;

int head[N], to[M], pre[N], nxt[M], val[M], cst[M], tot = 1;

int S = N - 1, T = N - 2, dis[N];

bool vis[N];

queue <int> q;

char *p1, *p2, buf[100000];

#define nc() (p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 100000, stdin), p1 == p2) ? EOF : *p1 ++ )

int rd() {
	int x = 0;
	char c = nc();
	while (c < 48) {
		c = nc();
	}
	while (c > 47) {
		x = (((x << 2) + x) << 1) + (c ^ 48), c = nc();
	}
	return x;
}

inline void add2(int x, int y, int z, int w) {
	to[ ++ tot] = y;
	nxt[tot] = head[x];
	val[tot] = z;
	cst[tot] = w;
	head[x] = tot;
}

inline void add(int x, int y, int z, int w) {
	add2(x, y, z, w);
	add2(y, x, 0, -w);
}

bool spfa() {
	while (!q.empty()) {
		q.pop();
	}
	memset(pre, 0, sizeof pre);
	memset(dis, 0x3f, sizeof dis);
	dis[S] = 0;
	q.push(S);
	while (!q.empty()) {
		int x = q.front();
		q.pop();
		vis[x] = false;
		for (int i = head[x]; i; i = nxt[i]) {
			if (dis[to[i]] > dis[x] + cst[i] && val[i]) {
				dis[to[i]] = dis[x] + cst[i];
				pre[to[i]] = i ^ 1;
				if (!vis[to[i]]) {
					vis[to[i]] = true;
					q.push(to[i]);
				}
			}
		}
	}
	return pre[T];
}

int mincost() {
	int re = 0;
	while (spfa()) {
		int mdl = 0x3f3f3f3f;
		for (int i = T; i != S; i = to[pre[i]]) {
			mdl = min(mdl, val[pre[i] ^ 1]);
		}
		for (int i = T; i != S; i = to[pre[i]]) {
			val[pre[i] ^ 1] -= mdl;
			val[pre[i]] += mdl;
			re += mdl * cst[pre[i] ^ 1];
		}
	}
	return re;
}

int A[110][110], id[110][110];

int main() {
	int n = rd();
	for (int i = 1; i <= n; i ++ ) {
		for (int j = 1; j <= n; j ++ ) {
			A[i][j] = rd();
		}
	}
	int cnt = n;
	for (int i = 1; i <= n; i ++ ) {
		for (int j = i + 1; j <= n; j ++ ) {
			if (A[i][j] == 2) {
				cnt ++ ;
				add(cnt, i, 1, 0);
				id[i][j] = tot - 1;
				add(cnt, j, 1, 0);
				id[j][i] = tot - 1;
				add(S, cnt, 1, 0);
			}
		}
	}
	// cout << tot << ' ' << cnt << endl ;
	for (int i = 1; i <= n; i ++ ) {
		int c = 0;
		for (int j = 1; j <= n; j ++ ) {
			if (A[i][j] == 1) {
				c ++ ;
			}
		}
		if (c) {
			add(S, i, c, 0);
		}
	}
	for (int i = 1; i <= n; i ++ ) {
		for (int j = 1; j <= n; j ++ ) {
			add(i, T, 1, j * (j - 1) / 2 - (j - 1) * (j - 2) / 2);
		}
	}
	int ans = mincost();
	ans = n * (n - 1) * (n -  2) / 6 - ans;
	cout << ans << endl ;
	for (int i = 1; i <= n; i ++ ) {
		for (int j = 1; j <= n; j ++ ) {
			if (A[i][j] != 2) {
				printf("%d ", A[i][j]);
			}
			else {
				if (i == j) {
					printf("0 ");
				}
				else {
					if (!val[id[i][j]]) {
						printf("1 ");
					}
					else {
						printf("0 ");
					}
				}
			}
		}
		puts("");
	}
	return 0;
}
/*
6
0 2 1 0 2 1
2 0 0 2 2 2
0 1 0 2 1 1
1 2 2 0 1 2
2 2 0 0 0 2
0 2 0 2 2 0

*/

小结:这个费用流很巧妙。就是有先后的选取过程,但是保证后面的代价比前面的代价大,我们可以暴力建边。 

posted @ 2019-08-27 20:51  JZYshuraK_彧  阅读(130)  评论(0编辑  收藏  举报