[WC2007]剪刀石头布 Solution

给一张竞赛图,图中还有一些边没有定向,现在要把它们重新定向,要求重定向之后三元环尽量多。
直接考虑不可取,逆向思维。算出极限情况可能出现三元环的个数,那么就是 n × ( n − 1 ) × ( n − 2 ) 6 \dfrac{n\times(n-1)\times(n-2)}{6} 6n×(n1)×(n2),接下来考虑什么情况会拆毁三元环。
考虑一个点的负场情况来判断少了多少三元环。
如果一个点负场为 2 2 2或更多,那么只要从能打过它的点里面随便挑出两个点就不是一个三元环。
按照本类题目的一般做法来做的话,把那些没定向的边看为点,称之为边对应的点,那么就可以这样建图:

  • 把源点和边对应的点连起来,容量为 1 1 1,费用为 0 0 0
  • 把边对应的点和这条边表示的双方连起来,容量为 1 1 1,费用为 0 0 0

那么之后边对应的点流向那个点就表明设定的是那个人输了。
那么接下来就要考虑人如何连到汇点了。直接做一条边显然是不可行的。
考虑一个点从 x x x负场到 x + 1 x+1 x+1负场会破坏多少三元环。
那么就是 x × ( x + 1 ) 2 − x × ( x − 1 ) 2 = x \dfrac{x\times(x+1)}{2}-\dfrac{x\times(x-1)}{2}=x 2x×(x+1)2x×(x1)=x
所以我们可以考虑拆边。
让一个人往汇点连上 n n n条边,第 i i i条边流量为 1 1 1,费用为 i i i
这样就可以了。
c o d e : 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;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值