bzoj 2597: [Wc2007]剪刀石头布

题意:

在一些一对一游戏的比赛(如下棋、乒乓球和羽毛球的单打)中,我们经常会遇到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个人参加一场这样的游戏的比赛,赛程规定任意两个人之间都要进行一场比赛:这样总共有场比赛。比赛已经进行了一部分,我们想知道在极端情况下,比赛结束后最多会发生多少剪刀石头布情况。即给出已经发生的比赛结果,而你可以任意安排剩下的比赛的结果,以得到尽量多的剪刀石头布情况。

题解:

好久没做网络流相关,感觉什么都不会了。
这题补集思想很重要,直接做不太可做,考虑有多少对是不符合的。
容易发现,当三个点不是三元环是,一定有以个点的出度为2,所以可以得到:

C3ninC2d[i]

d[i] 为i的出度,所以要让 niC2d[i] 最小。
然后就可以费用流了,st向每场比赛连边流量1,费用0。比赛向选手连边,流量1,费用0。然后每个选手向ed连n条边,流量都为1,费用是 c2i 的差分。
然后看哪条边满流就是谁赢。
code:

#include<cstdio>
#include<cstdlib>
#include<iostream>
#include<cstring>
#define LL long long
using namespace std;
struct node{
    int x,y,c,d,next;
}a[100000];int len=1,last[15000];
int n,map[110][110],num,st,ed,w[110][5],s[15000],q[15000],p[15000];
bool u[15000],c[110][110];
void ins(int x,int y,int c,int d)
{
    a[++len].x=x;a[len].y=y;a[len].c=c;a[len].d=d;
    a[len].next=last[x];last[x]=len;
    a[++len].x=y;a[len].y=x;a[len].c=0;a[len].d=-d;
    a[len].next=last[y];last[y]=len;
}
void pre()
{
    for(int i=0;i<=n;i++)
    {
        w[i][0]=1;
        for(int j=1;j<=3&&j<=i;j++) w[i][j]=w[i-1][j]+w[i-1][j-1];
    }
}
void insert(int x)
{
    for(int i=1;i<=n;i++)
        ins(x,ed,1,w[i][2]-w[i-1][2]);
}
bool spfa()
{
    memset(s,63,sizeof(s));
    memset(u,false,sizeof(u));
    int l=1,r=2;q[l]=st;s[st]=0;u[st]=true;
    while(l!=r)
    {
        int x=q[l];
        for(int i=last[x];i;i=a[i].next)
        {
            int y=a[i].y;
            if(s[y]>s[x]+a[i].d&&a[i].c>0)
            {
                s[y]=s[x]+a[i].d;
                p[y]=i;
                if(!u[y])
                {
                    u[y]=true;q[r]=y;
                    r++;if(r>ed+1) r=1;
                }
            }
        }
        u[x]=false;
        l++;if(l>ed+1) l=1; 
    }
    return s[ed]<999999999;
}
int flow() 
{
    int x=ed;
    int ans=0,min=-1;
    while(x!=st) 
    {
        int i=p[x];
        if (a[i].c<min||min==-1)min=a[i].c;
        x=a[i].x;
    }
    x=ed;
    while(x!=st) 
    {
        int i=p[x];
        a[i].c-=min;
        a[i^1].c+=min;
        x=a[i].x;
        ans=ans+a[i].d*min;
    }
    return ans;
}
int main()
{
    scanf("%d",&n);
    memset(c,false,sizeof(c));
    for(int i=1;i<=n;i++)
        for(int j=1;j<=n;j++)
        {
            scanf("%d",&map[i][j]);
            if(i<=j) continue;
            num++;
            if(map[i][j]==2)
            {
                ins(num+n,i,1,0);ins(num+n,j,1,0);
                map[i][j]=len-3;
            }
            else
            {
                c[i][j]=true;
                if(map[i][j]==1) ins(num+n,i,1,0);
                if(map[i][j]==0) ins(num+n,j,2,0);
            }
        }
    pre();
    st=0;ed=n+num+1;
    for(int i=1;i<=num;i++) ins(st,i+n,1,0);
    for(int i=1;i<=n;i++) insert(i);
    int ans=0;
    while(spfa()) ans+=flow();
    printf("%d\n",w[n][3]-ans);
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=n;j++)
        {
            if(i<=j)
            {
                if(c[j][i]) printf("%d ",1-map[j][i]);
                else printf("%d ",a[map[j][i]].c==0?0:1);
            }
            else
            {
                if(c[i][j]) printf("%d ",map[i][j]);
                else printf("%d ",a[map[i][j]].c==0?1:0);
            }
        }
        printf("\n");
    }
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值