POJ 2699 The Maximum Number of Strong Kings 网络流 竞赛图

题目大意:有n个人,两两之间有且仅比赛一次,每次比赛有且仅有一个人获胜,获胜的人可以得一分。定义一种人“strong king”,成为“strong king”的条件是,打败了所有得分更高的人,或得分就是最高的。现在,给你这n个人的最终分数,求最多有几个“strong king”。


首先考虑,假设确定了哪些人是“strong king”,哪些人不是(这个需要2^n的枚举)。在这个条件下,判断是否可行。构图。两组点,一组是人,一组是比赛。从源点到每个人建一条边,容量为这个人的得分。再建人到比赛的边,一场比赛(记为j点)对应两个人(记为i1和i2点),只有两种情况。一、这场比赛,随便i1,i2哪个获胜都行,建立i1到j和i2到j两条边,容量都为1。二、这场比赛,有个“strong king”,而且,另外个人得分比他高,这种情况,建立一条“strong king”到j的边,容量也是1。最后,建立每个比赛到汇点的边,容量为1。显然,如果满流,这个方案就是可行的。

优化。其实不用2^n枚举所有人。假设,k个“strong king”的方案是可行的,那么,最高得分的k(或者更多)个人是“strong king”一定是可行的。

证明。按分数从小到大排序。考虑序列1,2...i...j..n-1,.n。假设i为“strong king”,j不为“strong king”。若有一种方案使得,j为“strong king”(i不用考虑是否为“strong king”),那么,原命题则为真。从j+1到n,假设j输给了其中x个人。因为“strong king”的定义,i一定是赢了这x个人的。使得j赢了这x个人,i对这x的比赛改为输。j就成为了“strong king”。这么修改后,等于j多了x分,i少了x分。接着在1到j-1之间,找x个输给j的人(注意,这个x个人原来一定不是“strong king”,因为输给了分数更高的j),让这些比赛胜负取反。接着,除开“strong king”点,将与这些点有关的比赛的输赢不动,修改其他比赛情况,一定可以将得分调整好。这个不好证,我也说不太清楚。感觉应该没问题。因为除开“strong king”后,剩下的图等于没有了限制条件,得分的转移应该没有问题。如果有能说清楚的大神,请不吝赐教。


P.S. 输入的数字之间,居然有不止一个空格的情况。。。

//#pragma comment(linker, "/STACK:102400000,102400000")
#include<cstdio>
#include<cstring>
#include<vector>
#include<queue>
#include<cmath>
#include<cctype>
#include<string>
#include<algorithm>
#include<iostream>
#include<ctime>
#include<map>
#include<set>
using namespace std;
#define MP(x,y) make_pair((x),(y))
#define PB(x) push_back(x)
typedef __int64 LL;
//typedef unsigned __int64 ULL;
/* ****************** */
const int INF=1000111222;
const double INFF=1e200;
const double eps=1e-8;
const int mod=1000000007;
const int NN=150;
const int MM=4010;
/* ****************** */

char ss[1005];
int a[12];
struct G
{
    int v,cap,next;
}E[3005];
int p[NN],T;
int temp_p[NN],dd[NN],qw[NN];

void add(int u,int v,int cap)
{
    E[T].v=v;
    E[T].cap=cap;
    E[T].next=p[u];
    p[u]=T++;

    E[T].v=u;
    E[T].cap=0;
    E[T].next=p[v];
    p[v]=T++;
}

bool find_path(int st,int en,int n)
{
    int i,head,tail,u,v;
    for(i=0;i<=n;i++)
        dd[i]=-1;
    qw[head=tail=0]=st;
    dd[st]=0;
    while(head<=tail)
    {
        u=qw[head++];
        for(i=p[u];i+1;i=E[i].next)
        {
            v=E[i].v;
            if(dd[v]==-1 && E[i].cap>0)
            {
                dd[v]=dd[u]+1;
                qw[++tail]=v;
            }
        }
    }
    return (dd[en]!=-1);
}

int dfs_flow(int u,int&en,int f)
{
    if(u==en || f==0)
        return f;
    int temp,flow=0;
    for(;temp_p[u]+1; temp_p[u] = E[ temp_p[u] ].next)
    {
        G& e=E[ temp_p[u] ];
        if(dd[e.v]==dd[u]+1)
        {
            temp=dfs_flow(e.v,en,min(f,e.cap));
            if(temp>0)
            {
                e.cap-=temp;
                E[ temp_p[u]^1 ].cap+=temp;
                flow+=temp;
                f-=temp;
                if(f==0)
                    break;
            }
        }
    }
    return flow;
}

int dinic(int st,int en,int n)
{
    int i,ans=0;
    while( find_path(st,en,n) )
    {
        for(i=0;i<=n;i++)
            temp_p[i]=p[i];

        ans+=dfs_flow(st,en,INF);
    }
    return ans;
}


bool ok(int k,int n)
{
    memset(p,-1,sizeof(p));
    T=0;
    int i,j,tol=n;
    for(i=1;i<=n;i++)
        add(0,i,a[i]);
    for(i=1;i<=n;i++)
        for(j=i+1;j<=n;j++)
        {
            tol++;
            if(i+k>n && a[j]>a[i])
            {
                add(i,tol,1);
            }
            else
            {
                add(i,tol,1);
                add(j,tol,1);
            }
        }
    for(i=n+1;i<=tol;i++)
        add(i,tol+1,1);
    int ans=dinic(0,tol+1,tol+1);
    return (ans==n*(n-1)/2);
}

int main()
{
    int n,cas;
    int i,l,t;
    bool fuck;
    scanf("%d",&cas);
    getchar();
    while(cas--)
    {
        gets(ss);
        n=0;
        l=strlen(ss);
        t=0;
        fuck=false;
        for(i=0;i<=l;i++)
        {
            if(ss[i]>='0' && ss[i]<='9')
            {
                t=t*10+ss[i]-'0';
                fuck=true;
            }
            else
            {
                if(fuck)
                    a[++n]=t;
                fuck=false;
                t=0;
            }
        }
        sort(a+1,a+1+n);
        for(i=n;i>0;i--)
        {
            if( ok(i,n) )
                break;
        }
        printf("%d\n",i);
    }
    return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值