标签:oi bzoj 费用流
2597: [Wc2007]剪刀石头布
Time Limit: 20 Sec Memory Limit: 128 MBSec Special Judge
Submit: 492 Solved: 227
[Submit][Status]
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
费用流。
题目的要求就是对于一个竞赛图,给一些边定向使得长度为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。。。的边
【BZOJ 2597】 [Wc2007]剪刀石头布
标签:oi bzoj 费用流
原文地址:http://blog.csdn.net/regina8023/article/details/43481621