剪刀石头布
【问题描述】
在一些一对一游戏的比赛(如下棋、乒乓球和羽毛球的单打)中,我们经常会遇到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个人参加一场这样的游戏的比赛,赛程规定任意两个人之间都要进行一场比赛:这样总共有场比赛。比赛已经进行了一部分,我们想知道在极端情况下,比赛结束后最多会发生多少剪刀石头布情况。即给出已经发生的比赛结果,而你可以任意安排剩下的比赛的结果,以得到尽量多的剪刀石头布情况。
【输入文件】
输入文件的第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。
【输出文件】
输出文件的第1行是一个整数,表示在你安排的比赛结果中,出现了多少剪刀石头布情况。
输出文件的第2行开始有一个和输入文件中格式相同的N行N列的数字矩阵。第(i+1)行第j个数字描述了i和j之间的比赛结果,1表示i赢了j,0表示i负于j,与输入矩阵不同的是,在这个矩阵中没有表示比赛尚未进行的数字2;对角线上的数字都是0。输出矩阵要保证合法,不能发生矛盾。
【样例输入】
3
0 1 2
0 0 2
2 2 0
【样例输出】
1
0 1 0
0 0 1
1 0 0
【评分标准】
对于每个测试点,仅当你的程序的输出第一行的数字和标准答案一致,且给出了一个与之一致的合法方案,你才能得到该测试点的满分,否则该测试点得0分。
【数据规模】
30%的数据中,N ≤ 6;
100%的数据中,N ≤ 100。
题目:网上自己下数据吧
题意:在比赛图中安排输赢,使得最小环最多,即剪刀石头布情况
分析:这题从正面分析,看不出什么东西,我们可以利用对偶的性质,找不能构成剪刀石头布的情况,对于任意三个点,还有这三个点之间的三条边,如果满足其中一个点的入度为2,则这个图不是剪刀石头布情况,对于整个图中的一个点的入度in[i],那么有C(in[i],2)种情况不行,那么可以构成剪刀石头布情况的数量为C(n,3)-sum{C(i[i],2)}(1<=i<=n)
对这个式子进行转换,有S=n*(n-1)*(n-2)-sum{in[i]^2}/2+sum{in[i]}/2
由于S=n*(n-1)*(n-2)+sum{in[i]}/2是定值,所以只要保证sum{in[i]^2}/2的值最小即可
其实想到这里已经非常不容易了,其实我连这里都没想到= =
怎么保证这个和值最小呢?可以先固定所有边,然后调整使得值变小。。。这个方法没试过,我更惊讶的是题解的方法,居然是费用流??
构图,虚拟源点和汇点,还有没固定的边表示一种节点,另外就是n个节点,那么对于已经固定的边,在源点与相应顶点连上一条边,容量为1,费用为0,对于不固定的边,在源点与这条边对应节点连上容量为1,费用为0的边,这条边对应节点与这条边的两个端点分别连一条容量为1,费用为0的边,对于n个点,如何计算in[i]^2呢,也就是流量的平方?
题解通过巧妙的办法转换成费用来计算,
流量 1 2 3 4...n
费用 1 4 9 16...n^2
我们可以把这条边拆成n条容量为1的边,费用分别为 1,3,5,7,9。。。
然后求最小费用最大流
整道题的构思相当巧妙。。。Orz
PS:长春打铁了,发挥不好是一回事,感觉思维能力太差,最近想做点OI相关的题目,感觉很练思维,就算整道题的算法我都会,但是还是不会做啊,多思考,才能更强
代码:
#include<cstdio>
#include<iostream>
using namespace std;
const int mm=1111111;
const int mn=111111;
const int oo=1e9;
int node,src,dest,edge;
int ver[mm],flow[mm],cost[mm],next[mm];
int head[mn],dis[mn],q[mn],p[mn],vis[mn];
void prepare(int _node,int _src,int _dest)
{
node=_node,src=_src,dest=_dest;
for(int i=0;i<node;++i)head[i]=-1,vis[i]=0;
edge=0;
}
void addedge(int u,int v,int f,int c)
{
ver[edge]=v,flow[edge]=f,cost[edge]=c,next[edge]=head[u],head[u]=edge++;
ver[edge]=u,flow[edge]=0,cost[edge]=-c,next[edge]=head[v],head[v]=edge++;
}
bool spfa()
{
int i,u,v,l,r=0,tmp;
for(i=0;i<node;++i)dis[i]=oo;
dis[q[r++]=src]=0;
p[dest]=p[src]=-1;
for(l=0;l!=r;(++l>=mn)?l=0:l)
for(i=head[u=q[l]],vis[u]=0;i>=0;i=next[i])
if(flow[i]&&dis[v=ver[i]]>(tmp=dis[u]+cost[i]))
{
dis[v]=tmp;
p[v]=i^1;
if(vis[v])continue;
vis[q[r++]=v]=1;
if(r>=mn)r=0;
}
return p[dest]>=0;
}
int spfaflow()
{
int i,ret=0,delta;
while(spfa())
{
for(i=p[dest],delta=oo;i>=0;i=p[ver[i]])
if(flow[i^1]<delta)delta=flow[i^1];
for(i=p[dest];i>=0;i=p[ver[i]])
flow[i]+=delta,flow[i^1]-=delta;
ret+=delta*dis[dest];
}
return ret;
}
int a[111][111];
int i,j,k,l,n,m,ans;
int main()
{
while(~scanf("%d",&n))
{
for(m=0,i=1;i<=n;++i)
for(j=1;j<=n;++j)
{
scanf("%d",&a[i][j]);
if(i<j&&a[i][j]>1)++m;
}
prepare(m+n+2,0,m+n+1);
for(k=0,i=1;i<=n;++i)
for(j=i+1;j<=n;++j)
{
if(a[i][j]<2)
{
if(a[i][j])addedge(src,m+j,1,0);
else addedge(src,m+i,1,0);
}
else
{
++k;
addedge(src,k,1,0);
addedge(k,m+i,1,0);
addedge(k,m+j,1,0);
}
}
for(i=1;i<=n;++i)
for(j=1;j<=n;++j)
addedge(m+i,dest,1,j*2-1);
ans=n*(n-1)*(n-2)/3+n*(n-1)/2;
ans=(ans-spfaflow())/2;
for(k=0,i=1;i<=n;++i)
for(j=i+1;j<=n;++j)
if(a[i][j]>1)
for(l=head[++k];l>=0;l=next[l])
if(!flow[l])
{
if(ver[l]-m==i)a[j][i]=1,a[i][j]=0;
else a[i][j]=1,a[j][i]=0;
}
printf("%d\n",ans);
for(i=1;i<=n;++i)
for(j=1;j<=n;++j)
printf("%d%c",a[i][j],j<n?' ':'\n');
}
return 0;
}
本文探讨了在游戏开发中遇到的剪刀石头布情况,即在一对一游戏中出现的循环胜负关系。通过数学分析,我们了解了如何通过比赛结果的安排,最大化这种循环胜负情况的发生次数。
777

被折叠的 条评论
为什么被折叠?



