传送门biu~
一张竞赛图中,任意选三个点如果不形成环,那么一定有一个点的出度为2。也就是每当在任意一个点中找到任意两个出度,图中就会失去一个三元环。
令点
i
i
的出度为,则对于整张图来说,三元环的个数为
C3n−ΣC2degreei
C
n
3
−
Σ
C
d
e
g
r
e
e
i
2
。问题就变成了在边数恒定时如何使
ΣC2degreei
Σ
C
d
e
g
r
e
e
i
2
最小。可以看出,每当一个点
i
i
的加一,就会减少
degreei−1
d
e
g
r
e
e
i
−
1
个三元环。所以可以把每条边用一个点来表示,以出度为流量,构建最小费用最大流模型:
①
①
源点向每个原图中的节点连
n−1
n
−
1
条弧,流量均为
1
1
。费用分别为,代表着每次这个节点连出一条出边(即
degreei
d
e
g
r
e
e
i
++)时所减少的三元环数量。
②
②
每个原图中的节点向自己的出边连一条流量为,费用为
0
0
的弧。
每条边向汇点
T
T
连一条流量为,费用为
0
0
的弧。
求最小费用,再用减去即为所求的最多三元环的数量。求方案时只需要判断原图中边的辅助点是在哪个方向被流过的即可。
#include<bits/stdc++.h>
using namespace std;
const int inf=1e9;
int n,S,T,cost,a[105][105],Edge[105][105],dis[10105];
int head[10105],fir[10105],nex[60005],to[60005],cap[60005],val[60005],tp=1;
bool b[10105];
inline int calc(int x,int y){if(x>y)swap(x,y);return x*n+y;}
inline void add(int x,int y,int c,int v){
nex[++tp]=head[x];
head[x]=tp;
to[tp]=y;
cap[tp]=c;
val[tp]=v;
}
inline void Insert(int x,int y,int c,int v){add(x,y,c,v);add(y,x,0,-v);}
inline bool spfa(){
for(int i=S;i<=T;++i) dis[i]=inf,b[i]=0;
dis[S]=0;queue<int>q;q.push(S);
while(!q.empty()){
int x=q.front();q.pop();b[x]=false;
for(int i=head[x];i;i=nex[i]){
if(cap[i] && dis[x]+val[i]<dis[to[i]]){
dis[to[i]]=dis[x]+val[i];
if(!b[to[i]]) q.push(to[i]),b[to[i]]=true;
}
}
}
return dis[T]^inf;
}
int dfs(int x,int now){
if(x==T || now==0){
cost+=now*dis[T];
return now;
}
int c=0;b[x]=true;
for(int &i=fir[x];i;i=nex[i]){
if(!b[to[i]] && cap[i] && dis[to[i]]==dis[x]+val[i]){
int f=dfs(to[i],min(now,cap[i]));
now-=f;
cap[i]-=f;
cap[i^1]+=f;
c+=f;
if(!now) break;
}
}
return c;
}
inline void Dinic(){
while(spfa()){
for(int i=S;i<=T;++i) fir[i]=head[i];
dfs(S,inf);
}
}
int main(){
scanf("%d",&n);
for(int i=1;i<=n;++i)
for(int j=1;j<=n;++j)
scanf("%d",&a[i][j]);
S=0;T=n+n*n+1;
for(int i=1;i<=n;++i)
for(int j=0;j<=n-2;++j)
Insert(S,i,1,j);
for(int i=1;i<=n;++i){
for(int j=1;j<=n;++j){
if(a[i][j]) Insert(i,calc(i,j),1,0),Edge[i][j]=tp;
if(i<j) Insert(calc(i,j),T,1,0);
}
}
Dinic();
printf("%d\n",n*(n-1)*(n-2)/6-cost);
for(int i=1;i<=n;++i){
for(int j=1;j<=n;++j){
if(a[i][j]^2) printf("%d",a[i][j]);
else printf("%d",cap[Edge[i][j]]);
printf("%c",j==n?'\n':' ');
}
}
return 0;
}