BZOJ 2597: [Wc2007]剪刀石头布(费用流)
解题思路
考虑全集-不能构成三元环的个数。如果三个点不能构成三元环,一定有一个点的入度为\(2\),继续扩展,如果一个点的度数为\(3\),则会失去3个三元环。对于一个点来说,它所产生的不能构成三元环的贡献为\(C (deg[x],2)\),而度数每增加\(1\),对于答案的影响就是\(C(deg[x]+1,2)-C(deg[x],2)=deg[x]\),然后就可以建图了。考虑把边当做点,对于一条未确定的边来说,它只能对两个节点中的一个产生\(1\)个度数的贡献,所以让每个边向点连流量为1,费用为0的边。然后让源点向每条未确定的边连流量为1,费用为0的边。再让每个点向汇点连流量为\(1\),费用为\(deg[x],deg[x]+1,deg[x]+2,...n\)的边。跑一遍费用流。
代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<queue>
using namespace std;
const int MAXN = 100005;
const int MAXM = 500005;
const int inf = 0x3f3f3f3f;
inline int rd(){
int x=0,f=1;char ch=getchar();
while(!isdigit(ch)) f=ch=='-'?0:1,ch=getchar();
while(isdigit(ch)) x=(x<<1)+(x<<3)+ch-'0',ch=getchar();
return f?x:-x;
}
int n,head[MAXN],cnt=1,to[MAXM<<1],nxt[MAXM<<1],val[MAXM<<1],cost[MAXM<<1];
int deg[MAXN],num,S,T,op[105][105],dis[MAXN],incf[MAXN],pre[MAXN],ans,tmp[105][105];
bool vis[MAXN];
queue<int> Q;
inline void add(int bg,int ed,int w,int z){
to[++cnt]=ed,nxt[cnt]=head[bg],val[cnt]=w,cost[cnt]=z,head[bg]=cnt;
}
bool spfa(){
while(Q.size()) Q.pop();
memset(dis,0x3f,sizeof(dis));
memset(vis,false,sizeof(vis));
Q.push(S);vis[S]=1;incf[S]=inf;dis[S]=0;
while(Q.size()){
int x=Q.front();Q.pop();vis[x]=0;
for(int i=head[x];i;i=nxt[i]){
int u=to[i];
if(dis[x]+cost[i]<dis[u] && val[i]){
dis[u]=dis[x]+cost[i];
incf[u]=min(incf[x],val[i]);
pre[u]=i;
if(!vis[u]) vis[u]=1,Q.push(u);
}
}
}
return (dis[T]==inf)?0:1;
}
inline void update(){
int x=T,i;
while(x!=S){
i=pre[x];
val[i]-=incf[T];
val[i^1]+=incf[T];
x=to[i^1];
}
ans-=incf[T]*dis[T];
}
int main(){
n=rd();int x;T=n+2;S=n+1;num=T;
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++){
x=rd();op[i][j]=x;
if(x==1) deg[i]++;
}
for(int i=1;i<=n;i++) if(deg[i]>1) ans-=deg[i]*(deg[i]-1)/2;
for(int i=1;i<=n;i++)
for(int j=deg[i];j<=n;j++)
add(i,T,1,j),add(T,i,0,-j);
for(int i=1;i<=n;i++)
for(int j=i+1;j<=n;j++)
if(op[i][j]==2){
num++;add(S,num,1,0);add(num,S,0,0);
add(num,i,1,0),add(i,num,0,0);
add(num,j,1,0),add(j,num,0,0);
tmp[i][j]=tmp[j][i]=num;
}
while(spfa()) update();
ans+=n*(n-1)*(n-2)/6;
for(int i=1;i<=n;i++)
for(int j=i+1;j<=n;j++)
if(op[i][j]==2){
for(int k=head[tmp[i][j]];k;k=nxt[k]){
if(to[k]==S) continue;
if(!val[k]) {
if(to[k]==i) op[i][j]=1,op[j][i]=0;
else op[j][i]=1,op[i][j]=0;
}
}
}
printf("%d\n",ans);
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++)
printf("%d ",op[i][j]);
putchar('\n');
}
return 0;
}