题解:P4553 80人环游世界

前言

同学们读题一定要仔细我读错了四遍

题意

M M M 个人想尽一切办法让 N N N 个国家仅有 V i V_i Vi 个人拜访,同时要满足费用最小。

建图

提前定义一下 ( u , v , d o w n , u p , m o n e y ) (u,v,down,up,money) (u,v,down,up,money) u u u v v v 连下界为 d o w n down down 上界为 u p up up 费用为 m o n e y money money 的边。源点为 s s s,汇点为 t t t。附加源点为 s s ss ss,附加汇点 t t tt tt

显然有两个地方我们需要拆点:

  1. 每个人的开始地点。
  2. 国家与国家相连的边。

这两个地方我们不能保证一定小于或等于他们的定值,所以需要将拆成一个入点( a ′ a' a)一个出点 ( a a a)。

由“他们主要分布在东方” 中“分布” 一词可得出发点不一样所以 ( s ′ , s , 0 , m , 0 ) (s',s,0,m,0) (s,s,0,m,0) ( s , i ′ , 0 , i n f , 0 ) (s,i',0,inf,0) (s,i,0,inf,0) ( i , t , 0 , i n f , 0 ) (i,t,0,inf,0) (i,t,0,inf,0)

由“有且仅有 V i V_i Vi 个人会经过那一个国家” 可得 ( i ′ , i , V i , V i , 0 ) (i',i,V_i,V_i,0) (i,i,Vi,Vi,0)(这样便于不用想其他方式表示它)。

由“打算通过坐飞机” 可得每个国家连边为 ( i , ( i + j ) ′ , 0 , i n f , m o n e y i ) (i,(i+j)',0,inf,money_i) (i,(i+j),0,inf,moneyi)

然后跑一个有上下界最小费用最大流,就做完了。

代码

#include<bits/stdc++.h>
#define int long long
#define inf 2147483647
#define N 112345
using namespace std;
int n , m , s , t , ss , tt , s_ , head[N] , temp_head[N] , now[N] , dep[N] , cnt=0 , money , v[N] ;
bool f[N] ;
struct node{
	int to , next , w , c ;
}e[N<<2],temp[N<<2];
void use(int u,int v,int w,int c){
	e[cnt].to = v ;
	e[cnt].w = w ;
	e[cnt].c = c ;
	e[cnt].next = head[u] ;
	head[u] = cnt++ ;
}
void newnet(int u,int v,int w,int c){
	use(u,v,w,c) ;
	use(v,u,0,-c) ;
}
bool bfs(){
	for(int i=1;i<=n*3;i++) now[i] = head[i] , f[i] = 0 , dep[i] = inf ;
	queue<int> q ;
	q.push(s) ;
	dep[s] = 0 ;
	while(!q.empty()){
		int u=q.front() ;
		q.pop() ;
		f[u] = 0 ;
		for(int i=head[u];~i;i=e[i].next){
			int x=e[i].to ;
			if(dep[u]+e[i].c<dep[x]&&e[i].w){
				dep[x] = dep[u]+e[i].c ;
				if(!f[x]){
					f[x] = 1 ;
					q.push(x) ;
				}
			}
		}
	}
	return dep[t]!=inf ;
}
int dfs(int u,int sum){
	if(u==t) return sum ;
	if(f[u]) return 0 ;
	f[u] = 1 ;
	int use=0 ;
	for(int i=now[u];~i;i=e[i].next){
		now[u] = i ;
		int x=e[i].to ;
		if(!e[i].w||dep[u]+e[i].c!=dep[x]) continue ;
		int temp=dfs(x,min(sum,e[i].w)) ;
		if(!temp) continue ;
		e[i].w -= temp ;
		e[i^1].w += temp ;
		use += temp ;
		money += e[i].c*temp ;
		sum -= temp ;
		if(!sum) break ;
	}
	return use ;
}
int dinic(){
	int ans=0 ;
	while(bfs()) ans += dfs(s,inf) ;
	return ans ;
}
signed main(){
	memset(head,-1,sizeof head) ;
	cin >> n >> m ;
	s = n*2+1 ;t = s+1 ;ss = t+1 ;tt = ss+1 ; s_ = tt+1 ;
	newnet(s,s_,m,0) ;
	for(int i=1;i<=n;i++) scanf("%lld",v+i) ;
	for(int i=1;i<=n;i++) newnet(i+n,tt,v[i],0) , newnet(ss,i,v[i],0) , newnet(s_,i+n,m,0) ;
	for(int i=1;i<=n;i++){
		int u ;
		newnet(i,t,inf,0) ;
		for(int j=1;j<=n-i;j++){
			scanf("%lld",&u) ;
			if(u==-1) continue ;
			newnet(i,j+i+n,inf,u) ;
	
		}
	}
	newnet(t,s,inf,0) ;
	swap(ss,s) ;
	swap(tt,t) ;
	dinic() ;
	swap(ss,s) ;
	swap(tt,t) ;
	cout << money << endl ;
	return 0 ;
}
  • 14
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值