POJ 2699 The Maximum Number of Strong Kings(最大流)

558 篇文章 0 订阅
273 篇文章 0 订阅

POJ 2699 The Maximum Number of Strong Kings(最大流)

http://poj.org/problem?id=2699

题意:

       一场联赛可以表示成一个完全图,点表示参赛选手,任意两点u, v之间有且仅有一条有向边(u, v)或( v, u),表示u打败v或v打败u。一个选手的得分等于被他打败的选手总数。一个选手被称为“strong king”当且仅当他打败了所有比他分高的选手。分数最高的选手也是strong king。现在给出某场联赛所有选手的得分序列,由低到高,问合理安排每场比赛的结果后最多能有几个strong king。已知选手总数不超过10个。

分析:

       首先由于选手最多只有10个,那么我们可以枚举当前strong king的人数x. 假设对于给定的分数,我们枚举了x,那么这x个strong king分别对应给定分数序列的哪几个分数呢?

       网上有人说直接必定对应分数最高的那x个人,这种说法不一定正确.但是我们可以证明下列结论:对于一个分数序列,如果最多有kstrong kings,那么必然存在一种比赛情况,就是分数最大的k个人是strong kings.

       简略证明如下:

(转自http://blog.csdn.net/sdj222555/article/details/7797257)

假设序列是这个模样,1....i.....j.....k......n

假设只有i,k是strong kings,而j不是. 假设j输给了j+1至n区间中的x个人,那么显然i是赢了这x个人的,我们现在想把j变为strong kings.     那么就让把j输了的这些场全变成赢,此时j分值增加了x,就将j与i之前的人们的比赛多输x场,这样j的分数守恒了,但是j一赢之后,原本输给的x个人的分数少了,那就让他们都去赢i,这样他们的分数也就守恒了,此时发现i分数又不守恒了,少了x,恰好刚才j去输给i之前的人了x场,i正好去赢x场,这样大家的分数都守恒了。

       说完了上面的结论,我们每次只需要从大到小枚举分数最高的K个人做strong king即可.现在来建图:

       对于每场(ui,vi)之间的比赛i, S源点到比赛i有一条容量为1的边.

       对于每个选手u,从选手uT汇点有一条容量为score[u](u的分数)的边.

       对于每场比赛i==(ui,vi)来说:

如果score[ui]<score[vi]且ui是strong king,那么本次比赛结果就定了,必然是ui胜. 所以仅连比赛i 到ui的容量1的边.

       同样如果score[ui]>score[vi]且vi是strong king,那么本次比赛结果就定了,必然是vi胜. 所以仅连比赛i到vi的容量1的边.

       剩下所有情况都连比赛i分别到ui和vi的容量为1的两条边.(因为比赛结果我们不能肯定)

       最终计算最大流,看看该最大流是不是==n*(n-1)/2 ,如果是表示我们枚举的strong king人数合理.

       n为比赛人数.程序中用id[i][j]==id[j][i]来表示选手i和j之前进行的那场比赛在网络流图中的节点编号.

AC代码:

#include<cstdio>
#include<cstring>
#include<queue>
#include<vector>
#include<algorithm>
#include<cctype>
#define INF 1e9
using namespace std;
const int maxn= 1500+10;

struct Edge
{
    int from,to,cap,flow;
    Edge(){}
    Edge(int f,int t,int c,int fl):from(f),to(t),cap(c),flow(fl){}
};

struct Dinic
{
    int n,m,s,t;
    vector<Edge> edges;
    vector<int> G[maxn];
    int d[maxn];
    bool vis[maxn];
    int cur[maxn];

    void init(int n,int s,int t)
    {
        this->n=n,this->s=s,this->t=t;
        edges.clear();
        for(int i=0;i<n;i++) G[i].clear();
    }

    void AddEdge(int from,int to,int cap)
    {
        edges.push_back( Edge(from,to,cap,0) );
        edges.push_back( Edge(to,from,0,0) );
        m=edges.size();
        G[from].push_back(m-2);
        G[to].push_back(m-1);
    }

    bool BFS()
    {
        queue<int> Q;
        memset(vis,0,sizeof(vis));
        vis[s]=true;
        d[s]=0;
        Q.push(s);
        while(!Q.empty())
        {
            int x=Q.front(); Q.pop();
            for(int i=0;i<G[x].size();i++)
            {
                Edge& e=edges[G[x][i]];
                if(!vis[e.to] && e.cap>e.flow)
                {
                    vis[e.to]=true;
                    Q.push(e.to);
                    d[e.to]=d[x]+1;
                }
            }
        }
        return vis[t];
    }

    int DFS(int x,int a)
    {
        if(x==t || a==0) return a;
        int flow=0,f;

        for(int& i=cur[x]; i<G[x].size(); ++i)
        {
            Edge& e=edges[G[x][i]];
            if(d[e.to]==d[x]+1 && (f=DFS(e.to, min(a,e.cap-e.flow) ) )>0 )
            {
                e.flow +=f;
                edges[G[x][i]^1].flow -=f;
                flow +=f;
                a -=f;
                if(a==0) break;
            }
        }
        return flow;
    }

    int Max_Flow()
    {
        int ans=0;
        while(BFS())
        {
            memset(cur,0,sizeof(cur));
            ans += DFS(s,INF);
        }
        return ans;
    }
}DC;


int n,src,dst;//选手数,源点编号,汇点编号
int num;//流图的最大节点数目
int score[maxn];
int id[maxn][maxn];
void read()//读取每个实例的所有分数
{
    char s[1000];
    gets(s);
    n=0;
    for(int i=0;s[i];i++)
    {
        int temp=0;
        if( isdigit(s[i]) )
        {
            while(s[i] && isdigit(s[i]) )
            {
                temp=temp*10+s[i]-'0';
                ++i;
            }
            --i;//一定要减1,因为i可能跨越了字符串结束字符0
            score[++n]=temp;
        }
    }
}

bool solve(int k)
{
    DC.init(num,src,dst);
    for(int i=1;i<=n;i++) DC.AddEdge(i,dst,score[i]);
    for(int i=n+2;i<num;i++) DC.AddEdge(src,i,1);

    for(int i=1;i<=n;i++)
    for(int j=i+1;j<=n;j++)
    {
        if(score[i]<score[j] && i>= n-k+1) DC.AddEdge(id[i][j], i, 1);
        else if(score[j]<score[i] && j>= n-k+1) DC.AddEdge(id[i][j], j, 1);
        else
        {
            DC.AddEdge(id[i][j], i, 1);
            DC.AddEdge(id[i][j], j, 1);
        }
    }
    int ans=DC.Max_Flow();
    return ans == (n*(n-1)/2);
}

int main()
{
    int T;
    scanf("%d%*c",&T);
    while(T--)
    {
        read();
        src=0,dst=n+1;
        num=n+2;
        for(int i=1;i<=n;i++)
        for(int j=i+1;j<=n;j++)
            id[i][j]=id[j][i]=num++;

        for(int i=n;i>=0;i--)//枚举strong king可能的个数
        {
            if(solve(i))
            {
                printf("%d\n",i);
                break;
            }
        }
    }
    return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值