HDU3251 Being a Hero——最大流最小割(Dinic+输出割边)⭐

点这里

题意: 给定一个带权有向图,n个城市m条路(有边权),以及f个赠与你的城市(带有点权)。你需要从受赠与的城市中选择一些(可全部),并割断某些边将这些城市与其他城市隔开,你的最终受益等于所有受赠的点权和减去割掉的边权和。计算你的最大收益。并且输出割掉的边的编号。
题解: 要注意我们的收益是受赠与的城市的权值和减去割掉的边权和,而且必须要将你选择的城市与其他城市分割开。因此我们需要计算出能分割你的城市和其他的城市的最小边权和,而这个最小值,就是最大流的值。
  至于要打印割边的编号,那我们要清楚就算计算出了最大流,我们要舍弃哪些边。将你选择的城市看作一个连通分量T,将剩余的其他城市看作一割连通分量S,我们割的边,需要能连接这两个分量。


具体步骤:

  • 计算最大流: 这个只要用Dinic的模板就能计算,唯一要变通的就是设置一个超级汇点t = n + 1,将受赠的城市与超级汇点连接。
  • 分割: 利用Dinic中的残留容量图,对源点进行dfs,将所有残留容量不为零的边所连接的点,统统归入S的集合。如此一来,集合内的点即为连通分量S,而连接不到的点即为连通分量T。
  • 输出割边: 但是要注意!不是所有连接两个连通分量的边就一定是我们需要割的边。因为我们在图中加入了一个超级汇点t,所以我们还需要舍弃所以连接到超级汇点的边。

过程中犯的错:

  • init(): 我将超级汇点的设置放在了init函数中,而t = n + 1,必须在读入n之后才能进行初始化。
  • 割边需要舍弃连接超级汇点的边: 前面解释过原因。

#include<bits/stdc++.h>
using namespace std;
const int inf = 0x3f3f3f3f;
const int N = 1e3 + 10;
const int M = 2e5 + 10;

int T, n, m, f, k;
int s, t, cnt, sum;
int vis[N], cur[N], head[N], depth[N];
struct edge{	int v, w, next;} e[M];
void init(){	s = 1, t = n + 1, cnt = sum = 0;	for(int i = 0; i < N; i++)	vis[i] = 0, head[i] = -1;}
void add(int u, int v, int w){	e[cnt] = edge{v, w, head[u]};head[u] = cnt++;}
void addedge(int u, int v, int w){	add(u, v, w); add(v, u, 0);}
bool bfs(){
	memset(depth, 0, sizeof depth);
	queue<int> Q;
	Q.push(s); depth[s] = 1;
	while(!Q.empty()){
		int u = Q.front(); Q.pop();
		for(int i = head[u]; ~i; i = e[i].next){
			int v = e[i].v, w = e[i].w;
			if(depth[v] == 0 && w > 0){
				depth[v] = depth[u] + 1;
				Q.push(v);
			}
		}
	}
	if(depth[t] > 0)	return 1;
	return 0;
}
int dfs(int u, int flow){
	if(u == t)	return flow;
	for(int& i = cur[u]; ~i; i = e[i].next){
		int v = e[i].v, w = e[i].w;
		if(depth[v] == depth[u] + 1 && w != 0){
			int minflow = dfs(v, min(flow, w));
			if(minflow > 0){
				e[i].w -= minflow;
				e[i ^ 1].w += minflow;
				return minflow;
			}
		}
	}
	return 0;
}
int dinic(){
	int ans = 0;
	while(bfs()){
		for(int i = 1; i < N; i++)	cur[i] = head[i];
		while(int d = dfs(s, inf))	ans += d;
	}
	return ans;
}
void dfs(int u){
	for(int i = head[u]; ~i; i = e[i].next){
		int v = e[i].v;
		if(!vis[v] && e[i].w > 0)	vis[v] = 1,	dfs(v);
	}
}
void print_cut(){
	vis[1] = 1;	dfs(1);
	queue<int> Q;
	for(int i = 0; i < cnt; i += 2){
		int v = e[i].v, u = e[i ^ 1].v;
		if(vis[u] && !vis[v] && v != t)	Q.push(i);
	}
	printf("%d", Q.size());
	while(!Q.empty()){
		int u = Q.front(); Q.pop();
		printf(" %d", u / 2 + 1);
	}
	printf("\n");
}
int main(){
	scanf("%d", &T);
	while(T--){
		scanf("%d%d%d", &n, &m, &f);
		init();	//要在读入之后! 
		while(m--){
			int u, v, w;
			scanf("%d%d%d", &u, &v, &w);
			addedge(u, v, w);
		}
		while(f--){
			int u, w;
			scanf("%d%d", &u, &w);
			addedge(u, t, w);
			sum += w;
		}
		int ans = dinic();
		printf("Case %d: %d\n", ++k, sum - ans);
		print_cut();
	}
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值