[WC2007]剪刀石頭布 Solution

給一張競賽圖,圖中還有一些邊沒有定向,現在要把它們重新定向,要求重定向之後三元環儘量多。
直接考慮不可取,逆向思維。算出極限情況可能出現三元環的個數,那麼就是n×(n1)×(n2)6\dfrac{n\times(n-1)\times(n-2)}{6},接下來考慮什麼情況會拆毀三元環。
考慮一個點的負場情況來判斷少了多少三元環。
如果一個點負場爲22或更多,那麼只要從能打過它的點裏面隨便挑出兩個點就不是一個三元環。
按照本類題目的一般做法來做的話,把那些沒定向的邊看爲點,稱之爲邊對應的點,那麼就可以這樣建圖:

  • 把源點和邊對應的點連起來,容量爲11,費用爲00
  • 把邊對應的點和這條邊表示的雙方連起來,容量爲11,費用爲00

那麼之後邊對應的點流向那個點就表明設定的是那個人輸了。
那麼接下來就要考慮人如何連到匯點了。直接做一條邊顯然是不可行的。
考慮一個點從xx負場到x+1x+1負場會破壞多少三元環。
那麼就是x×(x+1)2x×(x1)2=x\dfrac{x\times(x+1)}{2}-\dfrac{x\times(x-1)}{2}=x
所以我們可以考慮拆邊。
讓一個人往匯點連上nn條邊,第ii條邊流量爲11,費用爲ii
這樣就可以了。
code:code:

#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;
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章