题目链接
F 发传单
题意:
一个人有 n n n 个朋友,这个人有很多传单,他可以将传单发给所有朋友,对于发给第 i i i 个人需要 w i w_i wi 的费用,他的朋友之间也有相互认识可以将传单给其他人,也需要一些费用,告诉你具体的朋友之间的认识关系和费用,要求这个人在使用最少的传单数下用最少的总费用,使得朋友都看过传单。
思路:
由于要求每一个朋友都看过,那么可以将每一个朋友进行拆点 ( i , i + n ) (i,i+n) (i,i+n) 那么这两点之间的流量一定要大于等于 1 1 1 ,将人做为源点 s s s 按照题意建边,建立一个汇点 t t t ,向所有的朋友右端点连向 t t t ,由于要求使用最小的传单数,参考一血的代码,可以将人向每一个朋友传单的费用变为 w i + i n f w_i+inf wi+inf ,最后 a n s % i n f ans\%inf ans%inf 就行了,因为是最小费用,所以可以使得所用的传单最小。现在就只要建立超级源点 S S S 和超级汇点 T T T 就可以跑有上下界的最小费用最大流了。
代码:
#include <bits/stdc++.h>
#define inf 0x3f3f3f3f
using namespace std;
const int N=1e5+10;
const int M=1e6+10;
const int mx=2e6;
struct MCMF{
int h[N],ne[M],to[M],flow[M],w[M],tot,dis[N],liu[N],pre[N],inq[N],s,t;
int ans;
void init(int a,int b){
s=a;t=b;
for(int i=0;i<M;i++)ne[i]=0;
for(int i=0;i<N;i++)h[i]=0;
tot=1;ans=0;
}
void addedge(int x,int y,int z,int c){
to[++tot]=y;ne[tot]=h[x],h[x]=tot,flow[tot]=z;w[tot]=c;
swap(x,y);
to[++tot]=y;ne[tot]=h[x],h[x]=tot,flow[tot]=0;w[tot]=-c;
}
int bfs(){
for(int i=0;i<=t;i++)dis[i]=inf,liu[i]=inf,inq[i]=pre[i]=0;
queue<int>q;int x;dis[s]=0;q.push(s);
while(!q.empty()){
x=q.front();q.pop();inq[x]=0;
for(int i=h[x];i;i=ne[i]){
int v=to[i];
if(flow[i]>0&&dis[x]+w[i]<dis[v]){
dis[v]=dis[x]+w[i],pre[v]=i;
liu[v]=min(liu[x],flow[i]);
if(!inq[v])inq[v]=1,q.push(v);
}
}
}
if(!pre[t])return 0;
x=t;ans+=dis[t]*liu[t];
while(x!=s){
int kl=pre[x];
flow[kl]-=liu[t];flow[kl^1]+=liu[t];x=to[kl^1];
}
return 1;
}
int mcmf(){
while(bfs()){}
return ans;
}
}F;
int d[N];
void add(int u,int v,int down,int up,int w){
F.addedge(u,v,up-down,w);
d[u]-=down,d[v]+=down;
}
int n,s,t,S,T;
int main()
{
scanf("%d",&n);s=2*n+1;t=2*n+2;S=2*n+3,T=2*n+4;
F.init(S,T);//初始化
add(t,s,0,inf,0);//变成无汇源
for(int i=1,x;i<=n;i++){
scanf("%d",&x);
add(s,i,0,inf,x+mx);
}
for(int i=1;i<=n;i++){
int tt,x,y;
add(i,i+n,1,inf,0);//下界大于一
add(i+n,t,0,inf,0);
scanf("%d",&tt);
while(tt--){
scanf("%d%d",&x,&y);
add(i+n,x,0,inf,y);
}
}
for(int i=1;i<=t;i++)if(d[i]>0)F.addedge(S,i,d[i],0);else if(d[i]<0)F.addedge(i,T,-d[i],0);
cout<<F.mcmf()%mx<<endl;
}