poj 2699(二分+最大流)较难

题意:

有n(n<=10)个竞赛者,进行了n*(n-1)/2次的比赛。已知每一个竞赛者在比赛中胜利的次数a1、a2、a3........an。(次序列是从小到大排序的),问你这n个人中最强的竞赛者最多有几个。(如果这个竞赛者胜利的次数最多或这个竞赛者打败了所有胜利次数比他多的竞赛者,那么这个竞赛者就是最强的。其中每两个人有且仅进行一场比赛。)


做法大概就是二分枚举最强者的个数(数量比较少,就直接枚举了。),越胜场多的成为强者的几率越高。所以枚举最强的k个人就是胜场最多的k个人

若a,b(a,b都属于枚举的最强者的集合)之间的比赛为c,且a的胜场数小于b的胜场数,则a向c连容量1的边;  表示a必须战胜b,否则a就不可能是最强者

对于其他的a,b和c,a,b分别向c连一条容量为1的边;  表示a,b谁赢谁输都无所谓

S向所有人连容量为此人胜场数的边,比赛c向T连容量1的边

最大流如果和比赛数目相等说明可以有k个人都是最强者


#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
#include <iostream>
#include <queue>
#include <stack>
using namespace std;
const int N = 100 ;
const int M = 1000 ;
const int inf=1<<30 ;
struct node
{
	int u,v,c,next;
}edge[M] ;
int head[N],dis[N],pre[N],cur[N],gap[N] ;
int re[N],top ,n;

void add(int u ,int v,int c)   
{  
    edge[top].u=u;  
    edge[top].v=v;  
    edge[top].c=c;  
    edge[top].next=head[u];  
    head[u]=top++;  
    edge[top].u=v;  
    edge[top].v=u;  
    edge[top].c=0;  
    edge[top].next=head[v];  
    head[v]=top++;  
}  

int sap(int s,int t,int nv)  
{  
    memset(dis,0,sizeof(dis));  
    memset(gap,0,sizeof(gap));  
    int u,v,minflow=inf,flow=0;  
    for(int i = 0 ; i <=nv ; i++)  cur[i]=head[i] ;  
    u=pre[s]=s;  
    gap[s]=nv;  
    while(dis[s] <= nv )  
    {  
            loop :  
               for(int &j=cur[u] ; j!=-1 ; j=edge[j].next)                 
               {  
                     v=edge[j].v ;  
                     if(edge[j].c > 0 && dis[u] == dis[v] +1 )  
                     {  
                              minflow = min(minflow ,edge[j].c) ;  
                              pre[v]=u;  
                              u=v ;  
                          if(v==t)  
                          {    
                                for( u =pre[v] ; v!=s ;v=u,u=pre[u])  
                                      {  
                                            edge[cur[u]].c -= minflow ;   
                                            edge[cur[u]^1].c += minflow ;  
                                      }   
                                flow += minflow ;  
                                minflow = inf ;  
                          }  
                         goto loop ;  
                     }  
               }  
           int mindis=nv ;  
           for(int i = head[u] ; i!=-1 ; i=edge[i].next)  
           {  
                  v=edge[i].v ;  
                  if(edge[i].c > 0 && dis[v] < mindis)  
                  {  
                       mindis = dis[v] ;  
                       cur[u]=i ;         
                 }  
           }  
          if(--gap[dis[u]]==0) break ;  
          gap[ dis[u] = mindis +1 ]++  ;  
          u=pre[u] ;  
    }  
    return flow ;  
}  

void init()
{
    char str[10000];
     gets(str) ;
		  int len=strlen(str) ;
		   n=0;
		  for( int i = 0 ; i < len; i ++)  
		  	 if(str[i] >='0' && str[i] <= '9')
		  	       re[++n]=str[i]-'0' ;
}
int solve()
{
    re[0] = -1;
    sort(re+1,re+n+1);  //胜利场数按小到大排个序 
    for(int mid = n ; mid > 0 ; mid--) //枚举mid~n这些人是强者,小于mid的不是 
    {
        memset(head,-1,sizeof(head));top=0;
        int m=(n-1)*n/2,s = 0,t=n+m+1;
        int tmp = n;
        for(int i=1;i<=n;i++) add(s,i,re[i]) ;//人和源点连边 
        for(int i=1;i<=n;i++)   
        {                     //每场比赛 
            for(int j=i+1;j<=n;j++)  //j始终比i大,如果i是强者,那j肯定是 
            {
                tmp++;    //比赛 
                add(tmp,t,1) ;  //比赛于汇点连边 
				if(i>=mid&&re[j]!=re[i]) //i这个人事强者并且i,j胜利场数不一样;          
                add(i,tmp,1) ;       //那么给i赢 
				else
				add(i,tmp,1),add(j,tmp,1) ;//i,j谁赢都没事 
            }
        }
        if(sap(s,t,t+1)< m )  //mid这个答案不符合 
        {
            return n-mid;    //强者是 mid+1  ~  n , 这些人 
        }
    }
    return n;
}
int main()
{
    int cas;
    scanf("%d",&cas); getchar();
    while(cas--)
    {
        init();
        printf("%d\n",solve());
    }
    return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值