给一张竞赛图,图中还有一些边没有定向,现在要把它们重新定向,要求重定向之后三元环尽量多。
直接考虑不可取,逆向思维。算出极限情况可能出现三元环的个数,那么就是
n
×
(
n
−
1
)
×
(
n
−
2
)
6
\dfrac{n\times(n-1)\times(n-2)}{6}
6n×(n−1)×(n−2),接下来考虑什么情况会拆毁三元环。
考虑一个点的负场情况来判断少了多少三元环。
如果一个点负场为
2
2
2或更多,那么只要从能打过它的点里面随便挑出两个点就不是一个三元环。
按照本类题目的一般做法来做的话,把那些没定向的边看为点,称之为边对应的点,那么就可以这样建图:
- 把源点和边对应的点连起来,容量为 1 1 1,费用为 0 0 0
- 把边对应的点和这条边表示的双方连起来,容量为 1 1 1,费用为 0 0 0
那么之后边对应的点流向那个点就表明设定的是那个人输了。
那么接下来就要考虑人如何连到汇点了。直接做一条边显然是不可行的。
考虑一个点从
x
x
x负场到
x
+
1
x+1
x+1负场会破坏多少三元环。
那么就是
x
×
(
x
+
1
)
2
−
x
×
(
x
−
1
)
2
=
x
\dfrac{x\times(x+1)}{2}-\dfrac{x\times(x-1)}{2}=x
2x×(x+1)−2x×(x−1)=x。
所以我们可以考虑拆边。
让一个人往汇点连上
n
n
n条边,第
i
i
i条边流量为
1
1
1,费用为
i
i
i。
这样就可以了。
c
o
d
e
:
code:
code:
#include <bits/stdc++.h>
#define regi register int
int n;
int S,T;
int ans;
int mincost;
int vis[100100];
int win[100100];
int dis[100100];
std::queue<int>q;
int a[1000][1000];
int be[10100][110];
int head[100100],tot=1;
struct edge{
int to;
int nxt;
int flow;
int cost;
}e[100000];
void add(int x,int y,int flow,int cost){
e[++tot]={y,head[x],flow,cost};
head[x]=tot;
e[++tot]={x,head[y],0,-cost};
head[y]=tot;
}
bool spfa(){
for(regi i=1;i<=T*3;++i){
dis[i]=0x3f3f3f3f;
vis[i]=0;
}
q.push(S);
dis[S]=0;
while(!q.empty()){
int x=q.front();
q.pop();
vis[x]=0;
for(regi i=head[x];i;i=e[i].nxt){
regi y=e[i].to;
if(e[i].flow&&dis[y]>dis[x]+e[i].cost){
dis[y]=dis[x]+e[i].cost;
if(!vis[y]){
vis[y]=1;
q.push(y);
}
}
}
}
return dis[T]!=0x3f3f3f3f;
}
int dfs(int x,int min){
if(x==T){
vis[T]=1;
return min;
}
vis[x]=1;
int flow=0;
for(regi i=head[x],w;i;i=e[i].nxt){
int y=e[i].to;
if((!vis[y]||y==T)&&e[i].flow&&dis[y]==dis[x]+e[i].cost)
if(w=dfs(y,std::min(min-flow,e[i].flow))>0){
mincost+=w*e[i].cost;
e[i].flow-=w;
e[i^1].flow+=w;
flow+=w;
}
}
return flow;
}
void dinic(){
while(spfa()){
vis[T]=1;
while(vis[T]){
memset(vis,0,sizeof vis);
dfs(S,0x3f3f3f3f);
}
}
}
main(){
scanf("%d",&n);
ans=n*(n-1)*(n-2)/6;
//全部的三元环。
S=1,T=n*n+n+2;
for(regi i=1;i<=n;++i)
for(regi j=1;j<=n;++j){
scanf("%d",&a[i][j]);
if(i>=j)
continue;
if(a[i][j]==1)
add(S,1+n*n+j,1,0);
if(a[i][j]==0)
add(S,1+n*n+i,1,0);
if(a[i][j]==2){
add(S,1+(i-1)*n+j,1,0);
//源点连点对应的边。
add(1+(i-1)*n+j,1+n*n+i,1,0);
add(1+(i-1)*n+j,1+n*n+j,1,0);
//点对应的边连对应的两个点。
}
}
//先算出现在少了多少三元环
for(regi i=1;i<=n;++i){
for(regi j=0;j<n;++j)
add(1+n*n+i,T,1,j);
}
//拆边,按照等差数列建边。
dinic();
//跑图。
printf("%d\n",ans-mincost);
for(regi i=1;i<=n;++i)
for(regi j=i+1;j<=n;++j){
if(a[i][j]<2)
continue;
for(regi k=head[1+(i-1)*n+j];k;k=e[k].nxt){
if(e[k].to&&e[k].flow==0)
a[i][j]=(e[k].to-n*n-1==j);
a[j][i]=a[i][j]^1;
}
}
//检索哪条边流满了。
for(regi i=1;i<=n;++i,puts(""))
for(regi j=1;j<=n;++j)
printf("%d ",a[i][j]);
return 0;
}