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 1≤T≤10,1≤n,m≤3000,1≤k≤n
题解
这道题可以用网络流来解决。
我们先建立一个最大流模型:
- 源点 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));
}
}