bzoj 2597 [Wc2007]剪刀石头布 费用流

题面

题目传送门

解法

建图方式有点意思……

  • 简单来说,题意就是确定一些无向边的方向,使得三元环的个数最多。
  • 考虑所有边都确定的情况下怎么计算三元环的个数,假设每一个点的度数为 d [ i ] d[i] d[i],通过容斥原理可以得到 ( n 3 ) − ∑ i = 1 n ( d [ i ] 2 ) {n\choose 3}-\sum_{i=1}^n{d[i]\choose 2} (3n)i=1n(2d[i])
  • 那么我们可以将那些没有确定的边按照编号提取出来,编号为 i i i的边连接 ( S , i , 1 , 0 ) (S,i,1,0) (S,i,1,0),然后 i i i连接两个端点 x , y x,y x,y ( i , x , 1 , 0 ) , ( i , y , 1 , 0 ) (i,x,1,0),(i,y,1,0) (i,x,1,0),(i,y,1,0)
  • 考虑如何维护后面 ∑ \sum 的部分,令 f ( i ) = ( d [ i ] 2 ) f(i)={d[i]\choose 2} f(i)=(2d[i]),不难发现 f ( i + 1 ) − f ( i ) > f ( i ) − f ( i − 1 ) f(i+1)-f(i)>f(i)-f(i-1) f(i+1)f(i)>f(i)f(i1)。那么可以让点 i i i T T T连一些边,费用分别为 d [ i ] , d [ i ] + 1 , … d[i],d[i]+1,\dots d[i],d[i]+1,。然后跑最小费用最大流。
  • 考虑算法的正确性。在处理后半部分费用的边时,一定先走费用小的,即一定可以保证 d d d所表示的度数没有流过时比 d d d大的也一定没有流过。

代码

#include <bits/stdc++.h>
using namespace std;
template <typename T> void chkmax(T &x, T y) {x = x > y ? x : y;}
template <typename T> void chkmin(T &x, T y) {x = x > y ? y : x;}
template <typename T> void read(T &x) {
	x = 0; int f = 1; char c = getchar();
	while (!isdigit(c)) {if (c == '-') f = -1; c = getchar();}
	while (isdigit(c)) x = x * 10 + c - '0', c = getchar(); x *= f;
}
const int N = 110, M = 10110, inf = 1 << 30;
int cnt, c[M], d[M], dis[M], pre[M], las[M], head[M], used[M], a[N][N], x[N][N];
struct Node {int x, y;} b[N * N];
struct Edge {int next, num, c, w;} e[M * N];
void add(int x, int y, int c, int w) {
	e[++cnt] = (Edge) {head[x], y, c, w};
	head[x] = cnt;
}
void Add(int x, int y, int c, int w) {add(x, y, c, w), add(y, x, 0, -w);}
bool spfa(int s, int t) {
	for (int i = s; i <= t; i++) dis[i] = inf, used[i] = 0;
	queue <int> q; q.push(s), dis[s] = 0;
	while (!q.empty()) {
		int x = q.front(); q.pop(), used[x] = 0;
		for (int p = head[x]; p; p = e[p].next) {
			int k = e[p].num, c = e[p].c, w = e[p].w;
			if (c && dis[k] > dis[x] + w) {
				dis[k] = dis[x] + w, pre[k] = x, las[k] = p;
				if (!used[k]) q.push(k), used[k] = 1;
			}
		}
	}
	return dis[t] != inf;
}
int EK(int s, int t) {
	int ret = 0;
	while (spfa(s, t)) {
		int fl = inf;
		for (int x = t; x != s; x = pre[x]) chkmin(fl, e[las[x]].c);
		for (int x = t; x != s; x = pre[x])
			e[las[x]].c -= fl, e[las[x] ^ 1].c += fl;
		ret += dis[t] * fl;
	}
	return ret;
}
int main() {
	int n, m = 0; read(n); cnt = 1;
	for (int i = 1; i <= n; i++) for (int j = 1; j <= n; j++) read(a[i][j]);
	for (int i = 1; i <= n; i++)
		for (int j = i + 1; j <= n; j++) {
			if (a[i][j] == 0) d[j]++, x[i][j] = 0, x[j][i] = 1;
			if (a[i][j] == 1) d[i]++, x[i][j] = 1, x[j][i] = 0;
			if (a[i][j] == 2) b[++m] = (Node) {i, j}, c[i]++, c[j]++;
		}
	int s = 0, t = n + m + 1, ans = n * (n - 1) * (n - 2) / 6;
	for (int i = 1; i <= m; i++)
		Add(s, i, 1, 0), Add(i, b[i].x + m, 1, 0), Add(i, b[i].y + m, 1, 0);
	for (int i = 1; i <= n; i++) {
		ans -= d[i] * (d[i] - 1) / 2;
		for (int j = 0; j < c[i]; j++) Add(i + m, t, 1, d[i] + j);
	}
	cout << ans - EK(s, t) << "\n";
	for (int i = 1; i <= m; i++)
		for (int p = head[i]; p; p = e[p].next) {
			int k = e[p].num, c = e[p].c;
			if (k && c) {
				int tx = k - m, ty = tx == b[i].x ? b[i].y : b[i].x;
				x[tx][ty] = 0, x[ty][tx] = 1;
			}
		}
	for (int i = 1; i <= n; i++, cout << "\n")
		for (int j = 1; j <= n; j++) cout << x[i][j] << ' ';
	return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值