【WC2007】剪刀石头布
费用流
题目大意:
给定一张竞赛图,有些边已经定向了,现在要给剩下的边定向,使得图中的三元环个数最多。求最多个数以及方案。
题解:
如果三个点之间的边不构成三元环,那么必然有一个点向另外两个点都有连边。我们把这个“不是三元环”的贡献,计算到出度为 $2$ 的那个点上。
考虑用总三元组个数减去不是三元环的个数。那么要求不是三元环的个数最少。
由于是竞赛图,任意三点都可能成环,所以总数为 $\binom n3$。
考虑一个点的出度如果为 $d$,那么任选两条出边,都会形成一个“不是三元环”的贡献。所以它的贡献为 $\frac{d(d-1)}{2}$。
然后我们知道,$\frac{d(d-1)}{2}=1+2+3+\dots+d$。
可以考虑费用流,每一条未定向的边,会选择流到两个点中的一个。对每个点,向汇点连容量为 $1$,费用为 $d_i,d_i+1,d_i+2,\dots,n$ 的边各一条,其中 $d_i$ 表示原有出度。
然后跑最小费用最大流。由于保证在同样流量的情况下费用最小,因此对每个点,都会先挑贡献小的边,那么就可以保证其贡献是正确的了。
满流下的费用就是最少的“不是三元环”的个数。
建出来的图每条边的容量均为 $1$,用 Dinic 费用流会非常快。
Code:
#include<iostream>
#include<algorithm>
#include<vector>
#include<cstring>
#include<queue>
using namespace std;
const int N=11005,M=200005,S=0;
int head[N],cnt=1,deg[105],T,ans,iter[N],dis[N],n,out[105][105];
bool vis[N];
vector<pair<int,int> >vec;
struct edge{
int to,nxt,cap,cost;
}e[M<<1];
inline void addedge(int u,int v,int cap,int cost){
e[++cnt]=(edge){v,head[u],cap,cost},head[u]=cnt;
e[++cnt]=(edge){u,head[v],0,-cost},head[v]=cnt;
}
queue<int>q;
int dfs(int u,int f){
if(!f||u==T)return f;
vis[u]=1;
int ret=0;
for(int&i=iter[u];i;i=e[i].nxt)
if(e[i].cap&&!vis[e[i].to]&&dis[e[i].to]==dis[u]+e[i].cost){
int d=dfs(e[i].to,min(f,e[i].cap));
e[i].cap-=d,e[i^1].cap+=d,ret+=d,f-=d;
if(!f)break;
}
vis[u]=0;
return ret;
}
bool MCMF(int&flow,int&cost){
memset(dis,0x3f,(T+2)<<2);
dis[S]=0;
memset(vis,0,sizeof(bool)*(T+2));
for(q.push(S);!q.empty();){
int u=q.front();q.pop();
vis[u]=0;
for(int i=head[u];i;i=e[i].nxt)
if(e[i].cap&&dis[e[i].to]>dis[u]+e[i].cost){
dis[e[i].to]=dis[u]+e[i].cost;
if(!vis[e[i].to])vis[e[i].to]=1,q.push(e[i].to);
}
}
if(dis[T]>1e9)return 0;
memcpy(iter,head,(T+2)<<2);
int dlt=dfs(S,0x3fffffff);
flow+=dlt,cost+=dis[T]*dlt;
return 1;
}
int main(){
ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
cin>>n;
for(int i=1;i<=n;++i){
for(int j=1;j<=n;++j){
int&x=out[i][j];
cin>>x;
if(i<j)
switch(x){
case 1:++deg[i];break;
case 0:++deg[j];break;
case 2:vec.push_back(make_pair(i,j));
}
}
}
T=n*n+n*2;
ans=n*(n-1)*(n-2)/6;
for(int d=1;d<=n;++d){
ans-=deg[d]*(deg[d]-1)>>1;
for(int i=deg[d];i<=n;++i)addedge(d,T,1,i);
}
int nod=n;
for(int i=0;i<vec.size();++i){
int u=vec[i].first,v=vec[i].second;
++nod;
addedge(S,nod,1,0);
addedge(nod,u,1,0);
addedge(nod,v,1,0);
}
int flow=0,cost=0;
while(MCMF(flow,cost));
ans-=cost;
cout<<ans<<'\n';
for(int i=0;i<vec.size();++i){
int to_u=head[n+1+i],to_v=e[to_u].nxt,u=e[to_u].to,v=e[to_v].to;
if(e[to_u].cap==0)out[u][v]=1,out[v][u]=0;else out[v][u]=1,out[u][v]=0;
}
for(int i=1;i<=n;++i)
for(int j=1;j<=n;++j)
cout<<out[i][j]<<" \n"[j==n];
return 0;
}