题解 [CF1264E] Beautiful League/[WC2007] 剪刀石头布

[CF1264E] Beautiful League
[WC2007] 剪刀石头布

真·网络流看题解乐趣尽失

  • 关于有向图三元环定向/计数:
    有一个巧妙的性质是合法三元环对于所有三条边(不考虑方向)补集转化后
    发现每个不合法方案中一定存在且仅存在一个点的出度为 2
    在完全图中尤其有用,可以直接转化为 \(\dbinom{n}{3}-\sum\dbinom{out_i}{2}\)

那么这个题就可以用这种方法来完成定向:
发现就是要不合法三元环尽量少
就是要让 \(\sum\frac{out_i(out_i-1)}{2}\) 尽量小

\[\begin{aligned} \sum\frac{out_i(out_i-1)}{2}&=\sum\frac{out_i^2-out_i}{2}\\ &=\frac{(\sum out_i^2)-总边数}{2} \end{aligned}\]

于是转为最小化 \(\sum out_i^2\)
那么每个点的费用函数都是凸的,每条边向与之相关的两个点之一产生 1 的流量
大力费用流即可

点击查看代码
#include <bits/stdc++.h>
using namespace std;
#define INF 0x3f3f3f3f
#define N 100010
#define fir first
#define sec second
#define ll long long
//#define int long long

char buf[1<<21], *p1=buf, *p2=buf;
#define getchar() (p1==p2&&(p2=(p1=buf)+fread(buf, 1, 1<<21, stdin)), p1==p2?EOF:*p1++)
inline int read() {
	int ans=0, f=1; char c=getchar();
	while (!isdigit(c)) {if (c=='-') f=-f; c=getchar();}
	while (isdigit(c)) {ans=(ans<<3)+(ans<<1)+(c^48); c=getchar();}
	return ans*f;
}

int n, m;
queue<int> q;
bool vis[N], lck[N], ans[55][55];
int head[N], mp[55][55], dis[N], inc[N], back[N], eid[1550][55], ecnt=1, s, t, tot;
struct edge{int to, next, flw, cst;}e[N<<1];
inline void add(int s, int t, int f, int c) {e[++ecnt]={t, head[s], f, c}; head[s]=ecnt;}

bool spfa(int s, int t) {
	memset(dis, 0x3f, sizeof(dis));
	memset(back, -1, sizeof(back));
	dis[s]=0; inc[s]=INF;
	q.push(s);
	while (q.size()) {
		int u=q.front(); q.pop();
		vis[u]=0;
		for (int i=head[u],v; ~i; i=e[i].next) {
			v = e[i].to;
			if (e[i].flw && dis[u]+e[i].cst<dis[v]) {
				dis[v]=dis[u]+e[i].cst;
				back[v]=i; inc[v]=min(inc[u], e[i].flw);
				if (!vis[v]) q.push(v), vis[v]=1;
			}
		}
	}
	return ~back[t];
}

signed main()
{
	tot=n=read(); m=read();
	memset(head, -1, sizeof(head));
	for (int i=1; i<=n; ++i)
		for (int j=i+1; j<=n; ++j)
			mp[i][j]=mp[j][i]=++tot;
	s=++tot, t=++tot;
	for (int i=1,u,v,id; i<=m; ++i) {
		u=read(); v=read();
		add(mp[u][v], v, 1, 0), add(v, mp[u][v], 0, 0);
		ans[u][v]=1;
		lck[mp[u][v]]=1;
	}
	for (int i=1; i<=n; ++i) {
		for (int j=i+1; j<=n; ++j) {
			add(s, mp[i][j], 1, 0), add(mp[i][j], s, 0, 0);
			if (!lck[mp[i][j]]) {
				eid[mp[i][j]][i]=ecnt+1;
				add(mp[i][j], i, 1, 0), add(i, mp[i][j], 0, 0);
				eid[mp[i][j]][j]=ecnt+1;
				add(mp[i][j], j, 1, 0), add(j, mp[i][j], 0, 0);
			}
		}
	}
	for (int i=1; i<=n; ++i)
		for (int j=1; j<=n; ++j)
			add(i, t, 1, (j-1)<<1|1), add(t, i, 0, -((j-1)<<1|1));
	memset(vis, 0, sizeof(vis));
	while (spfa(s, t)) {
		// ans+=inc[t]*dis[t];
		for (int u=t; u!=s; u=e[back[u]^1].to) {
			e[back[u]].flw-=inc[t];
			e[back[u]^1].flw+=inc[t];
		}
	}
	for (int i=1; i<=n; ++i) {
		for (int j=i+1; j<=n; ++j) if (!lck[mp[i][j]]) {
			// cout<<"ij: "<<i<<' '<<j<<endl;
			if (!e[eid[mp[i][j]][i]].flw) ans[j][i]=1;
			if (!e[eid[mp[i][j]][j]].flw) ans[i][j]=1;
		}
	}
	// cout<<"tem: "<<tem<<endl;
	// cout<<"ans: "<<ans<<endl;
	// ans=n*(n-1)*(n-2)/6-(ans-n*(n-1)/2)/2;
	// cout<<ans<<endl;
	for (int i=1; i<=n; ++i) {
		for (int j=1; j<=n; ++j) printf("%d", ans[i][j]);
		printf("\n");
	}

	return 0;
}
posted @ 2022-05-01 15:35  Administrator-09  阅读(1)  评论(0编辑  收藏  举报