sgu326 Perspective(最大流)

已知同一赛区的 N 只球队,现在知道他们现有得分,还有剩下的比赛场数(包括本赛区内的,和跨赛区的),以及赛区内部的对阵表,问 1 号球队能否获得赛区冠军(可以并列)。

大神的建图,我复述一遍,加深理解:

首先我们贪心选择 1 号球队剩下的比赛全部获胜,剩下的球队跨赛区的比赛全部输掉。如果这样,还是有球队的得分已经超过 1 号球队,那么就不用判断了,直接 “NO” 了。

如果一号球队还有戏的话,我们就开始建图:

从源点连到剩余球队的边(一号球队不用管了),权值为该球队和 1 号球队的胜场差。

如果球队 I 和球队 J 之间还有比赛,那么加一个点 P ,然后连三条边 I -> P ,J -> P,P -> 汇点,权值都为球队 I 和 球队 J 之间剩余的比赛场数,也就是mat[ i ][ j ]。

然后判断最大流是否等于所有和汇点相连的边的权值和,相等就是“YES”。

证明:源点发出的边的边权实质上是一个临界线,如果这些边满流,就意味着这些边所连接的球队和 1 号球队胜场数相等。而汇点处的边权表征的是剩余比赛场数,如果他们满流,意味着比赛已经打完。很显然,汇点处不满流,就表示至少有一组球队(有对阵关系的两只球队)得分和 1 号球队相等,而且还有比赛没有打完,那么无论那两只球队谁输谁赢,胜者得分必然超过 1 号球队。

代码:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
#define find_min(a,b) a<b?a:b
#define MAX 0x3f3f3f3f
using namespace std;

const int N = 30;
const int NN = 1000;
const int M = 30000;

struct Edge{
	int s,e,v,next;
}edge[M];

int e_num,sum,head[NN],d[NN],sp,tp;
int n,score[N],remain[N],mat[N][N];

void AddEdge(int a,int b,int c){
	edge[e_num].s=a; edge[e_num].e=b; edge[e_num].v=c;
	edge[e_num].next=head[a]; head[a]=e_num++;

	edge[e_num].s=b; edge[e_num].e=a; edge[e_num].v=0;
	edge[e_num].next=head[b]; head[b]=e_num++;
}

int bfs(){
	queue <int> q;
	memset(d,-1,sizeof(d));
	d[sp]=0;
	q.push(sp);
	while(!q.empty()){
		int cur=q.front();
		q.pop();
		for(int i=head[cur];i!=-1;i=edge[i].next){
			int u=edge[i].e;
			if(d[u]==-1 && edge[i].v>0){//没有标记,且可行流大于0
				d[u]=d[cur]+1;
				q.push(u);
			}
		}
	}
	return d[tp] != -1;//汇点是否成功标号,也就是说是否找到增广路
}

int dfs(int a,int b){//a为起点
	int r=0;
	if(a==tp)return b;
	for(int i=head[a];i!=-1 && r<b;i=edge[i].next){
		int u=edge[i].e;
		if(edge[i].v>0 && d[u]==d[a]+1){
			int x=find_min(edge[i].v,b-r);
			x=dfs(u,x);
			r+=x;
			edge[i].v-=x;
			edge[i^1].v+=x;
		}
	}
	if(!r)d[a]=-2;
	return r;
}

void dinic(int sp,int tp){
	int total=0,t;
	while(bfs()){
		while(t=dfs(sp,MAX))
		total+=t;
	}
	if(sum==total)puts("YES");
	else puts("NO");
}

int main()
{
	int i,j;
	while(~scanf("%d",&n))
	{
		memset(score,0,sizeof(score));
		memset(mat,0,sizeof(mat));
		memset(remain,0,sizeof(remain));
		for(i=1;i<=n;i++)
			scanf("%d",&score[i]);
		for(i=1;i<=n;i++)
			scanf("%d",&remain[i]);

		for(i=1;i<=n;i++){
			for(j=1;j<=n;j++)scanf("%d",&mat[i][j]);
		}

		score[1]+=remain[1];
		int flag=1;
		for(i=2;i<=n;i++){
			if(score[i]>score[1]){
				flag=0;break;
			}
		}
		if(!flag){
			puts("NO");continue;
		}

		int m=n;
		int id[N][N];
		for(i=2;i<=n;i++){
			for(j=i+1;j<=n;j++)if(mat[i][j]>0)id[i][j]=++m;
		}


		sum=0; e_num=0;
		memset(head,-1,sizeof(head));

		sp=1; tp=m+1;

		for(i=2;i<=n;i++){
			for(j=i+1;j<=n;j++){
				if(mat[i][j]>0){
					sum+=mat[i][j];
					AddEdge(i,id[i][j],mat[i][j]);
					AddEdge(j,id[i][j],mat[i][j]);
					AddEdge(id[i][j],tp,mat[i][j]);
				}
			}
		}
		for(i=2;i<=n;i++)
			AddEdge(sp,i,score[1]-score[i]);

		dinic(sp,tp);
	}
	return 0;
}


  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值