2597: [Wc2007]剪刀石头布
Time Limit: 20 Sec Memory Limit: 128 MBSec Special Judge
Submit: 732 Solved: 350
[Submit][Status][Discuss]
Description
在一些一对一游戏的比赛(如下棋、乒乓球和羽毛球的单打)中,我们经常会遇到A胜过B,B胜过C而C又胜过A的有趣情况,不妨形象的称之为剪刀石头布情况。有的时候,无聊的人们会津津乐道于统计有多少这样的剪刀石头布情况发生,即有多少对无序三元组(A, B, C),满足其中的一个人在比赛中赢了另一个人,另一个人赢了第三个人而第三个人又胜过了第一个人。注意这里无序的意思是说三元组中元素的顺序并不重要,将(A, B, C)、(A, C, B)、(B, A, C)、(B, C, A)、(C, A, B)和(C, B, A)视为相同的情况。
有N个人参加一场这样的游戏的比赛,赛程规定任意两个人之间都要进行一场比赛:这样总共有场比赛。比赛已经进行了一部分,我们想知道在极端情况下,比赛结束后最多会发生多少剪刀石头布情况。即给出已经发生的比赛结果,而你可以任意安排剩下的比赛的结果,以得到尽量多的剪刀石头布情况。
Input
输入文件的第1行是一个整数N,表示参加比赛的人数。
之后是一个N行N列的数字矩阵:一共N行,每行N列,数字间用空格隔开。
在第(i+1)行的第j列的数字如果是1,则表示i在已经发生的比赛中赢了j;该数字若是0,则表示在已经发生的比赛中i败于j;该数字是2,表示i和j之间的比赛尚未发生。数字矩阵对角线上的数字,即第(i+1)行第i列的数字都是0,它们仅仅是占位符号,没有任何意义。
输入文件保证合法,不会发生矛盾,当i≠j时,第(i+1)行第j列和第(j+1)行第i列的两个数字要么都是2,要么一个是0一个是1。
Output
输出文件的第1行是一个整数,表示在你安排的比赛结果中,出现了多少剪刀石头布情况。
输出文件的第2行开始有一个和输入文件中格式相同的N行N列的数字矩阵。第(i+1)行第j个数字描述了i和j之间的比赛结果,1表示i赢了j,0表示i负于j,与输入矩阵不同的是,在这个矩阵中没有表示比赛尚未进行的数字2;对角线上的数字都是0。输出矩阵要保证合法,不能发生矛盾。
Sample Input
3
0 1 2
0 0 2
2 2 0
Sample Output
1
0 1 0
0 0 1
1 0 0
HINT
100%的数据中,N≤ 100。
首先可以用容斥原理计算出答案:
前面的表示所有的组合,后面减去了不是三元环的情况,因为如果不是三元环,那么肯定有一个点的初度是2,所以用后面的式子就可以算出不是三元环的数量。
上面的那个式子化简一下就变成了
前面的两项是已知的,所以问题就转化成了求最后面那个式子的最小值。
这样就可以用费用流做了。对于费用是平方的问题,我们可以建多条变,权值分别为1,3,5…,这样最小费用的话肯定是先跑费用小的边,跑完之后就是平方得形式。
建图的时候需要保证从i赢了j,j就不能再赢i了。
所以对于每一对点i,j我们都建立一个代表这个点对的点,从原点向这个点连(1,0)的边,从这个点向i,j连(1,0)的边。
最后从每个点向汇点连n-1条(1,x)的边,其中x是上面说的那个等差数列。
#include<cstdio>
using namespace std;
#include<iostream>
#include<cstring>
#define D 100000
#define T n*n+n+2
#define inf 707406378
const int N=110;
const int M=100010;
bool f[M];
struct S{int st,en,va,co;}aa[M*10];
int n,point[M],next[M*10],dis[M],pre[M],map[N][N],win[N],tot=1,l[M];
inline void add(int x,int y,int va,int co){
next[++tot]=point[x];point[x]=tot;
aa[tot].st=x;aa[tot].en=y;aa[tot].va=va;aa[tot].co=co;
next[++tot]=point[y];point[y]=tot;
aa[tot].st=y;aa[tot].en=x;aa[tot].va=0;aa[tot].co=-co;
}
inline int SPFA(int x,int y){
int i,j,u,h,t;
memset(f,1,sizeof(f));
memset(dis,127/3,sizeof(dis));
h=0;t=1;l[t]=x;dis[x]=0;
while(h!=t){
h=h%D+1;u=l[h];f[u]=true;
for(i=point[u];i;i=next[i])
if(dis[aa[i].en]>dis[u]+aa[i].co&&aa[i].va>0){
dis[aa[i].en]=dis[u]+aa[i].co;
pre[aa[i].en]=i;
if(f[aa[i].en]){
f[aa[i].en]=false;
t=t%D+1;
l[t]=aa[i].en;
}
}
}
return dis[y]==inf?0:dis[y];
}
inline int ISAP(int x,int y){
int i,minn=inf;
for(i=y;i!=x;i=aa[pre[i]].st)
minn=min(minn,aa[pre[i]].va);
for(i=y;i!=x;i=aa[pre[i]].st){
aa[pre[i]].va-=minn;
aa[pre[i]^1].va+=minn;
}
return minn;
}
int main(){
int i,j,x,k;
scanf("%d",&n);
for(i=1;i<=n;++i){
for(j=1;j<=n;++j){
scanf("%d",&x);map[i][j]=x;
if(i==j) continue;
if(x==1) ++win[i];
if(x==2&&i<j){
add(1,(i-1)*n+j+1,1,0);
add((i-1)*n+j+1,i+n*n+1,1,0);
add((i-1)*n+j+1,j+n*n+1,1,0);
}
}
for(j=win[i]+1;j<=n;++j) add(i+n*n+1,T,1,j*2-1);
}
int ans=0,minn=1;
while(minn){
minn=SPFA(1,T);
if(minn) ans+=minn*ISAP(1,T);
}
for(i=1;i<=n;++i)
for(j=i+1;j<=n;++j)
if(map[i][j]==2)
for(k=point[(i-1)*n+j+1];k;k=next[k])
if(aa[k].va==0){
if(aa[k].en==i+n*n+1) map[i][j]=1,map[j][i]=0,++win[i];
if(aa[k].en==j+n*n+1) map[j][i]=1,map[i][j]=0,++win[j];
}
ans=n*(n-1)*(n-2)/6;
for(i=1;i<=n;++i) ans-=win[i]*(win[i]-1)/2;
printf("%d\n",ans);
for(i=1;i<=n;++i){
for(j=1;j<=n;++j) printf("%d ",map[i][j]);
printf("\n");
}
}