給一張競賽圖,圖中還有一些邊沒有定向,現在要把它們重新定向,要求重定向之後三元環儘量多。
直接考慮不可取,逆向思維。算出極限情況可能出現三元環的個數,那麼就是,接下來考慮什麼情況會拆毀三元環。
考慮一個點的負場情況來判斷少了多少三元環。
如果一個點負場爲或更多,那麼只要從能打過它的點裏面隨便挑出兩個點就不是一個三元環。
按照本類題目的一般做法來做的話,把那些沒定向的邊看爲點,稱之爲邊對應的點,那麼就可以這樣建圖:
- 把源點和邊對應的點連起來,容量爲,費用爲
- 把邊對應的點和這條邊表示的雙方連起來,容量爲,費用爲
那麼之後邊對應的點流向那個點就表明設定的是那個人輸了。
那麼接下來就要考慮人如何連到匯點了。直接做一條邊顯然是不可行的。
考慮一個點從負場到負場會破壞多少三元環。
那麼就是。
所以我們可以考慮拆邊。
讓一個人往匯點連上條邊,第條邊流量爲,費用爲。
這樣就可以了。
#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;
}