分析:
题目要求给未知边定向,使得图中有最多个三元环
一道看似和网络流完全没有关系的题,不过之前我们接触过一道图上问题用网络流解决的问题(无向边定向变欧拉路)
而这道题,我们也可以强扯到网络流上
我们的原则:正难则反
显然,整个图能产生的最多三元环有
C(n,3)
C
(
n
,
3
)
个,我们需要将不合法的子图减掉(补集法)
如果三个点无法形成环,那么ta一定长这样:
一个点有两个出度,一个点有两个入度,一个点有出度入度各一个
我们考虑有两个出度的这个点,设ta的出度为
degree[i]
d
e
g
r
e
e
[
i
]
那么与ta相关的不合法子图就有
C(degree[i],2)
C
(
d
e
g
r
e
e
[
i
]
,
2
)
显然我们要使 ∑ni=1C(degree[i],2) ∑ i = 1 n C ( d e g r e e [ i ] , 2 ) 尽量小(符合最小割)
那么我们就可以拆边使得费用(
degree[i]2−degree[i]2
d
e
g
r
e
e
[
i
]
2
−
d
e
g
r
e
e
[
i
]
2
)和流量(
degree[i]
d
e
g
r
e
e
[
i
]
)的关系符合上式
下面我们就可以用费用流计算每个点的
degree
d
e
g
r
e
e
,同时最小化花费(最小费用最大流)
对于每场比赛,已知的比赛就没有什么悬念了,而未知的比赛获胜者非此即彼,因此我们可以建图:
tip
同一场比赛不要重复建图
对于确定获胜的比赛,我们记录一下直接统计到
∑degree[i]2−degree[i]2
∑
d
e
g
r
e
e
[
i
]
2
−
d
e
g
r
e
e
[
i
]
2
中,就不要加入图中了
一T:边数太多,精简建图
二WA:发现是SPJ,就这样吧
#include<cstdio>
#include<cstring>
#include<iostream>
#include<queue>
using namespace std;
const int INF=0x33333333;
const int N=15000;
const int M=50010;
struct node{
int x,y,v,c,nxt;
};
node way[M];
int st[N],tot=-1,n,s,t,pre[N],dis[N],ans[102][102],w[102],Ans=0;
bool in[N];
void add(int u,int w,int z,int cc) {
tot++;way[tot].x=u;way[tot].y=w;way[tot].v=z;way[tot].c=cc;way[tot].nxt=st[u];st[u]=tot;
tot++;way[tot].x=w;way[tot].y=u;way[tot].v=0;way[tot].c=-cc;way[tot].nxt=st[w];st[w]=tot;
}
int spfa(int s,int t) {
memset(in,0,sizeof(in));
memset(dis,0x33,sizeof(dis));
queue<int> q;
q.push(s); dis[s]=0; in[s]=1;
while (!q.empty()) {
int now=q.front(); q.pop();
in[s]=0;
for (int i=st[now];i!=-1;i=way[i].nxt)
if (way[i].v&&dis[way[i].y]>dis[now]+way[i].c) {
dis[way[i].y]=dis[now]+way[i].c;
pre[way[i].y]=i;
if (!in[way[i].y]) {
in[way[i].y]=1;
q.push(way[i].y);
}
}
}
return dis[t]!=INF;
}
void solve() {
while (spfa(s,t))
{
int sum=INF;
for (int i=t;i!=s;i=way[pre[i]].x)
sum=min(sum,way[pre[i]].v);
Ans=Ans+sum*dis[t];
for (int i=t;i!=s;i=way[pre[i]].x)
way[pre[i]].v-=sum,
way[pre[i]^1].v+=sum;
}
Ans=n*(n-1)*(n-2)/6-Ans;
for (int i=1;i<=tot;i++)
if (way[i].x>n&&way[i].x<t&&way[i].y!=s&&!way[i].v){
int tmp=way[i].x-n;
int x=(tmp-1)/n+1,y=tmp-(x-1)*n;
if (way[i].y==y) swap(y,x);
ans[x][y]=1,ans[y][x]=0;
}
printf("%d\n",Ans);
for (int i=1;i<=n;i++) {
for (int j=1;j<=n;j++)
printf("%d ",ans[i][j]);
printf("\n");
}
}
int main()
{
memset(st,-1,sizeof(st));
scanf("%d",&n);
s=0,t=n*n+n+1;
for (int i=1;i<=n;i++)
for (int j=1;j<=n;j++) {
int num=(i-1)*n+j+n;
int x; scanf("%d",&x);
if (x==1) w[i]++,ans[i][j]=1; //确定获胜
else if (x==2&&i<j) { //同一场比赛不要重复添加
add(s,num,1,0);
add(num,i,1,0);
add(num,j,1,0);
}
}
for (int i=1;i<=n;i++) {
Ans+=w[i]*(w[i]-1)/2;
for (int j=w[i];j<n;j++)
add(i,t,1,j);
}
solve();
return 0;
}