Luogu P4249 【[WC2007]剪刀石头布】

Description

传送门


Solution

首先发现直接求这种三元组不打好求,那么考虑球不满足这种关系的三元组的数量。

注意到一个三元组,如果不满足这种关系,肯定分别赢了\(0\)\(1\)\(2\)场。

那么我们如果知道了每个人赢的场数\(y_i\),不具有这种关系的三元组数量就是\(\sum \frac{y_i \times (y_i - 1}{2}\)

因为要使满足这种关系的三元组数量最多,所以我们建立的合法方案要使\(\sum \frac{y_i \times (y_i - 1}{2}\)最小。考虑用网络流来求解。

首先要满足是一种合法方案,可以这样建模,首先每个人向\(T\)连一条容量为\(n\)的有向边,然后对于一个还没有发生的比赛,新建一个节点\(tmp\),从\(tmp\)分别向\(i\)\(j\)连一条容量为\(1\)的有向边,从\(S\)\(tmp\)链接一条容量为\(1\)的有向边。这样建模跑出来的最大流就是一种合法方案。

现在考虑如何在跑最大流的时候使\(\sum \frac{y_i \times (y_i - 1)}{2}\)最小,将每个人向\(T\)连的有向边拆成容量为\(1\)的若干条有向边,费用分别为\(0\)\(1\)\(2 \dots\)这样连边是因为如果一个人的胜利场数增加一,\(\frac{y_i \times (y_i - 1}{2}\)的变化会呈现一种等差数列的形式,而这样将边拆开,每次只会增广费用最小的一条路,所以能保证正确性。

需要注意的是,如果一个人本来有一些胜利场数,要在原先的胜利场次基础上进行拆边和建模。


Code

#include <iostream>
#include <cstdio>
#include <cstring>
#include <queue>
using namespace std;

#define int long long

const int INF = 999999999;
const int N = 1000050;
const int M = 2000050;

int n, s, t, head[N], cur[N], d[N], vis[N], a[500][500], num = 1, cnt, y[N], ans, maxflow, mincost, neww[N][3], diao;

struct Node
{
	int next, to, flow, dis;
} edge[M * 2];

void Addedge(int u, int v, int w, int c)
{
	edge[++num] = (Node){ head[u], v, w, c};
	head[u] = num;
}

void Add(int u, int v, int w, int c)
{
	Addedge(u, v, w, c);
	Addedge(v, u, 0, -c);
	return; 
}

template <class T>
void Read(T &x)
{
	x = 0; int p = 0; char st = getchar();
	while (st < '0' || st > '9') p = (st == '-'), st = getchar();
	while (st >= '0' && st <= '9') x = (x << 1) + (x << 3) + st - '0', st = getchar();
 	x = p ? -x : x;
 	return;
}

template <class T>
void Put(T x)
{
	if (x < 0) putchar('-'), x = -x;
	if (x > 9) Put(x / 10);
	putchar(x % 10 + '0');
	return; 
}

void Work(int x)
{
	for (int i = y[x] + 1; i <= n; i++)
	{
		Add(x, t, 1, i - 1);
	}
	return;
}

int Bfs()
{
	queue<int> q;
	for (int i = 0; i <= cnt; i++) d[i] = INF, vis[i] = 0;
	d[s] = 0; vis[s] = 1; q.push(s);
	while (!q.empty())
	{
		int u = q.front(); q.pop(); vis[u] = 0;
		for (int i = head[u]; i; i = edge[i].next)
			if (edge[i].flow > 0 && d[edge[i].to] > d[u] + edge[i].dis)
			{
				d[edge[i].to] = d[u] + edge[i].dis;
				if (!vis[edge[i].to])
				{
					vis[edge[i].to] = 1;
					q.push(edge[i].to);
				}
			}	
	} 
	return d[t] != INF;
}

int Dinic(int x, int flow)
{
	if (x == t || !flow) return flow;
	int rest = flow, k;
	vis[x] = 1;
	for (int i = cur[x]; i && rest; i = edge[i].next)
	{
		int v = edge[i].to;
		cur[x] = i;
		if (!vis[edge[i].to] && edge[i].flow > 0 && d[edge[i].to] == d[x] + edge[i].dis)
		{
			int v = edge[i].to;
			int k = Dinic(v, min(rest, edge[i].flow));
		//	if (!k) dis[edge[i].to] = INF;
			rest -= k;
			edge[i].flow -= k;
			edge[i ^ 1].flow += k;
			mincost += k * edge[i].dis;
		}
	}
	vis[x] = 0;
	return flow - rest;
}

void Solve()
{
	while(Bfs())
	{
		for (int i = 0; i <= cnt; i++) cur[i] = head[i]; 
		maxflow += Dinic(s, INF);
	}
	return; 
}

signed main()
{
	Read(n);
	cnt = n + 1;
	for (int i = 1; i <= n; i++) 
		for (int j = 1; j <= n; j++)
		{
			Read(a[i][j]);
			if (a[i][j] == 1) y[i]++;
		}
	s = 0; t = n + 1;
	for (int i = 1; i <= n; i++) ans += y[i] * (y[i] - 1) / 2, Work(i);
	for (int i = 1; i <= n; i++)
		for (int j = i + 1; j <= n; j++)
			if (a[i][j] == 2)
			{
				int tmp = ++cnt;
				neww[++diao][1] = i;
				neww[diao][2] = j; 
				Add(s, tmp, 1, 0);
				Add(tmp, j, 1, 0); 
				Add(tmp, i, 1, 0);
				neww[diao][0] = num;
			} 
	Solve();
	ans += mincost;
	Put(n * (n - 1) * (n - 2) / 6 - ans); putchar('\n');
	for (int i = 1; i <= diao; i++) a[neww[i][1]][neww[i][2]] = edge[neww[i][0]].flow > 0 ? 1 : 0, a[neww[i][2]][neww[i][1]] = a[neww[i][1]][neww[i][2]] ^ 1;
 	for (int i = 1; i <= n; i++) 
	{
		for (int j = 1; j <= n; j++)
			Put(a[i][j]), putchar(' ');
		putchar('\n');
	}
	return 0;
}
posted @ 2020-06-12 10:39  Tian-Xing  阅读(74)  评论(0编辑  收藏  举报