洛谷 P4249 [WC2007]剪刀石头布

在一些一对一游戏的比赛(如下棋、乒乓球和羽毛球的单打)中,我们经常会遇到 \(A\) 胜过 \(B\)\(B\) 胜过 \(C\)\(C\) 又胜过 \(A\) 的有趣情况,不妨形象的称之为剪刀石头布情况。有的时候,无聊的人们会津津乐道于统计有多少这样的剪刀石头布情况发生,即有多少对无序三元组 \((A,B,C)\),满足其中的一个人在比赛中赢了另一个人,另一个人赢了第三个人而第三个人又胜过了第一个人。注意这里无序的意思是说三元组中元素的顺序并不重要,将 \((A, B, C)\)\((A, C, B)\)\((B, A, C)\)\((B, C, A)\)\((C, A, B)\)\((C, B, A)\) 视为相同的情况。

\(N\) 个人参加一场这样的游戏的比赛,赛程规定任意两个人之间都要进行一场比赛:这样总共有 \(\frac{N*(N-1)}{2}\) 场比赛。比赛已经进行了一部分,我们想知道在极端情况下,比赛结束后最多会发生多少剪刀石头布情况。即给出已经发生的比赛结果,而你可以任意安排剩下的比赛的结果,以得到尽量多的剪刀石头布情况。

\(N \leq 100\)


简单来说就是给你一个竞赛图,有一些边没有定向,你要定向之后使得图中的三元环个数最多。

那么考虑把问题反过来,最小化非三元环子图个数,用 \(n\choose{3}\) 来减去。

于是对三元环定向,只考虑入度,然后可以发现,一个点任意两个入度都会贡献 \(1\) ,那么一个入度为 \(x\) 的点的贡献就是 \(x\choose2\) ,这样子一条未定向就被我们转化成了谁的入度加 \(1\) ,直接二分图匹配就可以了, \(x\choose2\) 差分建边就可以了。

Code

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#define fi first
#define se second
const int N = 100;
using namespace std;
int n,a[N + 5][N + 5],S,T,idc,id[N + 5],d[N + 5],t[N + 5],ans,id2[N + 5][N + 5];
namespace F
{
	const int N = 3e6;
	const long long inf = 2e18;
	#define mp make_pair
	struct edges
	{
		int v;
		long long w,f;
	}edge[N + 5];
	int head[N + 5],S,T,nxt[N + 5],edge_cnt = 1,cur[N + 5],vis[N + 5],p[N + 5],q[N + 5];
	long long dis[N + 5],cost;
	void add_edge(int u,int v,long long w,long long f)
	{
		edge[++edge_cnt] = (edges){v,w,f};
		nxt[edge_cnt] = head[u];
		head[u] = edge_cnt;
	}
	void add(int u,int v,long long w,long long f)
	{
		add_edge(u,v,w,f);
		add_edge(v,u,0,-f);
	}
	bool spfa()
	{
		for (int i = 1;i <= idc;i++)
			dis[i] = inf,cur[i] = head[i],vis[i] = p[i] = 0;
		int l = 1,r = 0;
		dis[S] = 0;
		q[++r] = S;
		while (l <= r)
		{
			int u = q[l++];
			vis[u] = 0;
			for (int i = head[u];i;i = nxt[i])
			{
				int v = edge[i].v;
				long long w = edge[i].w,f = edge[i].f;
				if (w && dis[v] > dis[u] + f)
				{
					dis[v] = dis[u] + f;
					if (!vis[v])
					{
						q[++r] = v;
						vis[v] = 1;
					}
				}
			}
		}
		return dis[T] != inf;
	}
	long long dfs(int u,long long flow)
	{
		if (u == T)
			return flow;
		long long sm = 0;
		p[u] = 1;
		for (int &i = cur[u];i;i = nxt[i])
		{
			int v = edge[i].v;
			long long w = edge[i].w,f = edge[i].f;
			if (w && dis[v] == dis[u] + f && !p[v])
			{
				long long res = dfs(v,min(flow,w));
				edge[i].w -= res;
				edge[i ^ 1].w += res;
				flow -= res;
				sm += res;
				cost += res * f;
				if (!flow)
					break;
			}
		}
		p[u] = 0;
		return sm;
	}
	pair <long long,long long> dinic(int s,int t)
	{
		S = s;T = t;
		long long ans = 0;
		cost = 0;
		while (spfa())
			ans += dfs(S,inf);
		return mp(ans,cost);
	}
	void clear()
	{
		for (int i = 1;i <= idc;i++)
			head[i] = 0;
		edge_cnt = 1;
		idc = 0;
	}
	void solve()
	{
		for (int i = 1;i <= n;i++)
			for (int j = i + 1;j <= n;j++)
				if (a[i][j] == 2)
				{
					for (int k = head[id2[i][j]];k;k = nxt[k])
					{
						int v = edge[k].v,w = edge[k].w;
						if (v != S && !w)
						{
							if (id[j] == v)
								a[i][j] = 1,a[j][i] = 0;
							else
								a[i][j] = 0,a[j][i] = 1;
						}
					}
				}
	}
}
int C2(int x)
{
	return x * (x - 1) / 2;
}
int C3(int x)
{
	return x * (x - 1) * (x - 2) / 6;
}
int main()
{
	scanf("%d",&n);
	for (int i = 1;i <= n;i++)
		for(int j = 1;j <= n;j++)
			scanf("%d",&a[i][j]);
	for (int i = 1;i <= n;i++)
		for (int j = i + 1;j <= n;j++)
		{
			if (a[i][j] == 1)
				d[j]++,t[j]++;
			if (a[i][j] == 0)
				d[i]++,t[i]++;
			if (a[i][j] == 2)
				t[i]++,t[j]++;
		}
	S = ++idc;T = ++idc;
	for (int i = 1;i <= n;i++)
	{
		id[i] = ++idc;
		for (int j = d[i] + 1;j <= t[i];j++)
			F::add(id[i],T,1,C2(j) - C2(j - 1));
		ans += C2(d[i]);
	}
	for (int i = 1;i <= n;i++)
		for (int j = i + 1;j <= n;j++)
			if (a[i][j] == 2)
			{
				id2[i][j] = ++idc;
				F::add(S,idc,1,0);
				F::add(idc,id[i],1,0);
				F::add(idc,id[j],1,0);
			}
	ans = C3(n) - (F::dinic(S,T).se + ans);
	cout<<ans<<endl;
	F::solve();
	for (int i = 1;i <= n;i++)
	{
		for (int j = 1;j <= n;j++)
			printf("%d ",a[i][j]);
		putchar(10);
	}
	return 0;
}
posted @ 2021-03-31 07:09  eee_hoho  阅读(70)  评论(0编辑  收藏  举报