2597: [Wc2007]剪刀石头布
Time Limit: 20 Sec Memory Limit: 128 MBSec Special Judge
Submit: 492 Solved: 227
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。
费用流。
题目的要求就是对于一个竞赛图,给一些边定向使得长度为3的环最多。
先进行补集转化:使得非三元环的数量最少。
可以发现在三个点中,若一个点入度为2,那么一定无法构成三元环。
ans
=C(n,3)-min(sigma(C(indegree[i],2)))
=n*(n-1)*(n-2)/6+sigma(indegree[i])/2-min(sigma(indegree[i]^2))/2
=n*(n-1)*(n-2)/6+n*(n-1)/4-min(sigma(indegree[i]^2))/2
前两部分是常数,所以最小化sigma(indegree[i]^2)即可。
对于x^2建模方法是连费用为1,3,5,7,9。。。的边,那么连n条边的和就是n^2了(根据n^2-(n-1)^2证明)
对于已知边,直接从s-t连流量为1,费用为上述建模方法;
每条未知边看作一个点x,从s到x连流量为1,费用为0的边;再从x向对战双方连流量为1费用为0的边。
最后对于每个人向t连若干条流量为1,费用最小为(已知入度k)1+2k,最大为1+2(n-1)的边。
求费用流即可。
#include <iostream> #include <cstdio> #include <cstring> #include <algorithm> #include <cstdlib> #define inf 0x3f3f3f3f #define M 100000 #include <queue> using namespace std; int cnt=0,tot=1,in[M],n,s,t,h[M],d[M],inq[M],p[M],f[M],a[105][105]; struct edge { int from,to,cap,flow,cost,ne; }E[200005]; void read(int &tmp) { tmp=0; int fu=1; char ch=getchar(); for (;ch<'0'||ch>'9';ch=getchar()) if (ch=='-') fu=-1; for (;ch>='0'&&ch<='9';ch=getchar()) tmp=tmp*10+ch-'0'; tmp*=fu; } void Addedge(int from,int to,int cap,int cost) { E[++tot]=(edge){from,to,cap,0,cost,h[from]}; h[from]=tot; E[++tot]=(edge){to,from,0,0,-cost,h[to]}; h[to]=tot; } bool spfa(int &flow,int &cost) { for (int i=s;i<=t;i++) d[i]=inf,inq[i]=0; d[s]=0,inq[s]=1,p[s]=0,f[s]=inf; queue<int> q; q.push(s); while (!q.empty()) { int x=q.front(); q.pop(); inq[x]=0; for (int i=h[x];i;i=E[i].ne) { edge &e=E[i]; if (e.cap>e.flow&&d[e.to]>d[x]+e.cost) { d[e.to]=d[x]+e.cost; p[e.to]=i; f[e.to]=min(f[x],e.cap-e.flow); if (!inq[e.to]) q.push(e.to),inq[e.to]=1; } } } if (d[t]==inf) return false; flow+=f[t]; cost+=d[t]*f[t]; int u=t; while (u!=s) { E[p[u]].flow+=f[t]; E[p[u]^1].flow-=f[t]; u=E[p[u]].from; } return true; } int mincost() { int flow=0,cost=0; while (spfa(flow,cost)); return cost; } void Print() { int now=0; for (int i=1;i<=n;i++) for (int j=i+1;j<=n;j++) if (a[i][j]==2) { now++; for (int k=h[now];k;k=E[k].ne) if (E[k].flow==1&&(E[k].to==cnt+i||E[k].to==cnt+j)) { if (E[k].to==cnt+i) a[i][j]=0,a[j][i]=1; else a[i][j]=1,a[j][i]=0; } } for (int i=1;i<=n;i++) { for (int j=1;j<n;j++) printf("%d ",a[i][j]); printf("%d\n",a[i][n]); } } int main() { read(n); cnt=0; s=0; for (int i=1;i<=n;i++) for (int j=1;j<=n;j++) { read(a[i][j]); if (i<j&&a[i][j]==2) cnt++; if (a[i][j]==1) in[j]++; } t=cnt+n+1; int now=0; for (int i=1;i<=n;i++) for (int j=i+1;j<=n;j++) if (a[i][j]==2) Addedge(s,++now,1,0), Addedge(now,cnt+i,1,0), Addedge(now,cnt+j,1,0); for (int i=1;i<=n;i++) { int k=1; now=0; while (now<in[i]*in[i]) { Addedge(s,t,1,k); now+=k,k+=2; } for (;k<=2*n;k+=2) Addedge(cnt+i,t,1,k); } int k=(n*(n-1))/4-mincost()/2; printf("%d\n",n*(n-1)*(n-2)/6+k); Print(); return 0; }
感悟:
1.tle是在连边的地方<in[i]*in[i]写成<in[i]了
2.对于x^2的建模是练1,3,5,7,9。。。的边