[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 */
小结:这个费用流很巧妙。就是有先后的选取过程,但是保证后面的代价比前面的代价大,我们可以暴力建边。
| 欢迎来原网站坐坐! >原文链接<