洛谷 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;
}