[WC2007] 剪刀石头布
$solution:$
考虑如何求完全图上的三元环个数,考虑对于 $(i,j,k)$ ,若其不是三元环则必有一个的度数为 $2$ ,则总个数为 $$\dbinom{n}{3}-\sum_{i=1}^n \dbinom{d_i}{2}$$
若要求式子最小,则 $$min\{\sum_{i=1}^n \dbinom{d_i}{2}\}=min\{\sum_{i=1}^n d_i^2\}$$
那么问题便转化为了给每条边定向,使得每个点度数平方的和最小,对于二选一问题考虑网络流求解。维护平方差量,每次增加 $1^2-0^2,2^2-1^2,3^2-2^2,4^2-3^2$ 等,跑最小费用最大流即可。
#include<iostream> #include<cstring> #include<cstdio> #include<algorithm> #include<queue> #include<climits> using namespace std; inline int read(){ int f=1,ans=0;char c=getchar(); while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();} while(c>='0'&&c<='9'){ans=ans*10+c-'0';c=getchar();} return f*ans; } const int MAXN=50001; const int MAXM=301; queue<int> que; struct node{ int u,v,w,cost,nex; }x[MAXN<<1]; int head[MAXN],cnt,N,M,MM[MAXM][MAXM],S,T,INF=INT_MAX,Me[MAXN][MAXM]; void add(int u,int v,int w,int cost){ x[cnt].u=u,x[cnt].v=v,x[cnt].w=w,x[cnt].cost=cost,x[cnt].nex=head[u],head[u]=cnt++;swap(u,v),w=0,cost=-cost; x[cnt].u=u,x[cnt].v=v,x[cnt].w=w,x[cnt].cost=cost,x[cnt].nex=head[u],head[u]=cnt++;return; } pair<int,int> pa[MAXN]; int num,dis[MAXN],vis[MAXN],MC; bool bfs(){ memset(vis,0,sizeof(vis)),memset(dis,127/3,sizeof(dis));int inf=dis[0];dis[S]=0; vis[S]=1;que.push(S); while(!que.empty()){ int xx=que.front();que.pop();vis[xx]=0; for(int i=head[xx];i!=-1;i=x[i].nex){ if(dis[x[i].v]>dis[xx]+x[i].cost&&x[i].w){ dis[x[i].v]=dis[xx]+x[i].cost; if(!vis[x[i].v]) vis[x[i].v]=1,que.push(x[i].v); } } }return dis[T]!=inf; } int dfs(int u,int flow){ if(u==T) return flow;int used=0;vis[u]=1; for(int i=head[u];i!=-1;i=x[i].nex){ if(!vis[x[i].v]&&x[i].w&&dis[x[i].v]==dis[u]+x[i].cost){ int slow=dfs(x[i].v,min(flow-used,x[i].w));used+=slow;MC+=slow*x[i].cost; x[i].w-=slow,x[i^1].w+=slow; if(flow==used) break; } }if(!used) dis[u]=-1; return used; } int Dinic(){ int Ans=0; while(bfs()){memset(vis,0,sizeof(vis));Ans+=dfs(S,INF);} return Ans; } int Ans[MAXM][MAXM]; int GG[MAXM][MAXM],d[MAXN]; int calc(int x){if(x<2) return 0;return (x*(x-1))/2;} int main(){ // freopen("7.in","r",stdin); memset(head,-1,sizeof(head)); N=read(); for(int i=1;i<=N;i++) for(int j=1;j<=N;j++){ int e=read(); if(e==1) MM[i][j]=1; if(e==0) MM[i][j]=2; } num=N; for(int i=1;i<=N;i++){ for(int j=i+1;j<=N;j++){ pa[++num].first=i,pa[++num].second=j;GG[i][j]=num; if(MM[i][j]==1){add(num,i,1,0);continue;} if(MM[i][j]==2){add(num,j,1,0);continue;} add(num,i,1,0),add(num,j,1,0); } } for(int i=N+1;i<=num;i++) add(S,i,1,0);T=++num; for(int i=1;i<=N;i++){ for(int j=1;j<=N;j++) add(i,T,1,j*j-(j-1)*(j-1)); } int G=Dinic(); for(int i=0;i<cnt;i++) if(x[i].u>N&&x[i].v<=N) Me[x[i].u][x[i].v]=x[i].w; for(int i=1;i<=N;i++){ for(int j=i+1;j<=N;j++){ if(MM[i][j]==1){Ans[i][j]=1;continue;} if(MM[i][j]==2){Ans[i][j]=0;continue;} int las=GG[i][j]; // printf("%d %d %d\n",las,i,j); if(Me[las][i]==1){Ans[i][j]=0;continue;} Ans[i][j]=1;continue; } } for(int i=1;i<=N;i++) for(int j=i+1;j<=N;j++) Ans[j][i]=Ans[i][j]^1; for(int i=1;i<=N;i++) for(int j=1;j<=N;j++) if(Ans[i][j]) d[j]++; int res=(N*(N-1)*(N-2))/6; for(int i=1;i<=N;i++) res-=calc(d[i]); printf("%d\n",res); for(int i=1;i<=N;i++){ for(int j=1;j<=N;j++) printf("%d ",Ans[i][j]); printf("\n"); }return 0; }/*3 0 1 2 0 0 2 2 2 0 */