2023“钉耙编程”中国大学生算法设计超级联赛(2)Coin

2023“钉耙编程”中国大学生算法设计超级联赛(2)Coin

题目大意

F F F组织了一场游戏,有 n n n个人参与。开始时,每人都有一枚硬币,且每人都有一个数 a i a_i ai表示他能持有硬币的上限。

游戏有 m m m轮,在每一轮中,小 F F F会选择不同的两个人 A A A B B B,然后这两个人有三种选择:

  • A A A B B B一枚硬币
  • B B B A A A一枚硬币
  • 他们什么都不做

在这 n n n个人中,有 k k k个人是小 F F F的朋友。现在小 F F F想知道,在经过 m m m轮游戏后,在所有可能的情况下,他的 k k k个朋友所持有的硬币的最大总数是多少。

T T T组数据。

1 ≤ T ≤ 10 , 1 ≤ n , m ≤ 3000 , 1 ≤ k ≤ n 1\leq T\leq 10,1\leq n,m\leq 3000,1\leq k\leq n 1T10,1n,m3000,1kn


题解

这道题可以用网络流来解决。

我们先建立一个最大流模型:

  • 源点 S S S向每一个人连一条流量为 1 1 1的边
  • 将每个人按时间顺序拆成 m m m个点, m m m个点从前往后依次连一条流量为 a i a_i ai的边
  • 对于每次操作给定的 ( A , B ) (A,B) (A,B),在各自对应时间点上的点连两条流量为 1 1 1的边(正向边流量为 1 1 1,反向边流量也为 1 1 1
  • 对于 k k k个朋友点,他们的最后一个时间的点向汇点 T T T连一条流量为 a i a_i ai的边

求一个最大流,即可得到答案。

但是,这样建图的话,点的数量和边的数量都为 O ( n × m ) O(n\times m) O(n×m),我们考虑优化。

我们发现,在图上很多点都是没有用的。所以,我们并不需要将每个人都拆成 m m m个点,只需在每个时间将给定的 ( A , B ) (A,B) (A,B)中的 A A A B B B都新开一个点,再连边即可。

这样的话,点数为 O ( m ) O(m) O(m),边数为 O ( m + k ) O(m+k) O(m+k),时间复杂度不超过 O ( n × m ) O(n\times m) O(n×m)

code

#include<bits/stdc++.h>
using namespace std;
const int inf=1e9;
int tq,n,m,k,x,y,s,t,cnt=0,a[3005],b[3005],lst[3005];
int tot=1,d[100005],l[100005],r[100005],w[100005];
int ans,dis[100005],vd[100005];
void add(int xx,int yy,int zz){
	l[++tot]=r[xx];d[tot]=yy;r[xx]=tot;w[tot]=zz;
}

int aug(int i,int augco){
	if(i==t) return augco;
	int augc=augco,md=cnt-1,dl;
	for(int u=r[i];u;u=l[u]){
		int j=d[u];
		if(w[u]>0){
			if(dis[i]==dis[j]+1){
				dl=aug(j,min(augc,w[u]));
				augc-=dl;
				w[u]-=dl;
				w[u^1]+=dl;
				if(dis[s]>=cnt) return augco-augc;
				if(!augc) break;
			}
			md=min(md,dis[j]);
		}
	}
	if(augco==augc){
		--vd[dis[i]];
		if(!vd[dis[i]]) dis[s]=cnt;
		dis[i]=md+1;
		++vd[dis[i]];
	}
	return augco-augc;
}
void sap(){
	memset(dis,0,sizeof(dis));
	memset(vd,0,sizeof(vd));
	ans=0;vd[0]=cnt;
	while(dis[s]<cnt){
		ans+=aug(s,inf);
	}
}
int main()
{
	scanf("%d",&tq);
	while(tq--){
		scanf("%d%d%d",&n,&m,&k);
		s=++cnt;t=++cnt;
		for(int i=1;i<=n;i++){
			scanf("%d",&a[i]);
			lst[i]=++cnt;
			add(s,lst[i],1);add(lst[i],s,0);
		}
		for(int i=1;i<=m;i++){
			scanf("%d%d",&x,&y);
			add(lst[x],++cnt,a[x]);add(cnt,lst[x],0);
			lst[x]=cnt;
			add(lst[y],++cnt,a[y]);add(cnt,lst[y],0);
			lst[y]=cnt;
			add(lst[x],lst[y],1);add(lst[y],lst[x],1);
		}
		for(int i=1;i<=k;i++){
			scanf("%d",&b[i]);
			add(lst[b[i]],t,a[b[i]]);add(t,lst[b[i]],0);
		}
		sap();
		printf("%d\n",ans);
		tot=1;cnt=0;
		memset(r,0,sizeof(r));
	}
}
  • 5
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值