【bzoj2561】最小生成树 网络流最小割
题目描述
给定一个边带正权的连通无向图G=(V,E),其中N=|V|,M=|E|,N个点从1到N依次编号,给定三个正整数u,v,和L (u≠v),假设现在加入一条边权为L的边(u,v),那么需要删掉最少多少条边,才能够使得这条边既可能出现在最小生成树上,也可能出现在最大生成树上?
输入
第一行包含用空格隔开的两个整数,分别为N和M;
接下来M行,每行包含三个正整数u,v和w表示图G存在一条边权为w的边(u,v)。
最后一行包含用空格隔开的三个整数,分别为u,v,和 L;
数据保证图中没有自环。
接下来M行,每行包含三个正整数u,v和w表示图G存在一条边权为w的边(u,v)。
最后一行包含用空格隔开的三个整数,分别为u,v,和 L;
数据保证图中没有自环。
输出
输出一行一个整数表示最少需要删掉的边的数量。
样例输入
3 2
3 2 1
1 2 3
1 2 2
样例输出
1
题解
网络流最小割
考虑Kruscal求最小生成树的过程:按照长度从小到大枚举每条边,如果某条边的两个点没有被连通,则加入这条边。
所以某条边出现在最小生成树上的条件是:所有长度小于它的边不能使得这两点连通。
于是可以把所有长度小于L的边加入到图中,然后要使得u和v不连通,就是求最小割。跑一遍即可。
最大生成树同理。
最后把两边的答案加起来即为最小删掉的边数。
#include <cstdio> #include <cstring> #include <queue> #define N 20010 #define M 400010 using namespace std; queue<int> q; int x[M] , y[M] , z[M] , head[N] , to[M] , val[M] , next[M] , cnt = 1 , s , t , dis[N]; void add(int x , int y , int z) { to[++cnt] = y , val[cnt] = z , next[cnt] = head[x] , head[x] = cnt; to[++cnt] = x , val[cnt] = z , next[cnt] = head[y] , head[y] = cnt; } bool bfs() { int x , i; memset(dis , 0 , sizeof(dis)); while(!q.empty()) q.pop(); dis[s] = 1 , q.push(s); while(!q.empty()) { x = q.front() , q.pop(); for(i = head[x] ; i ; i = next[i]) { if(val[i] && !dis[to[i]]) { dis[to[i]] = dis[x] + 1; if(to[i] == t) return 1; q.push(to[i]); } } } return 0; } int dinic(int x , int low) { if(x == t) return low; int temp = low , i , k; for(i = head[x] ; i ; i = next[i]) { if(val[i] && dis[to[i]] == dis[x] + 1) { k = dinic(to[i] , min(temp , val[i])); if(!k) dis[to[i]] = 0; val[i] -= k , val[i ^ 1] += k; if(!(temp -= k)) break; } } return low - temp; } int main() { int n , m , i , w , ans = 0; scanf("%d%d" , &n , &m); for(i = 1 ; i <= m ; i ++ ) scanf("%d%d%d" , &x[i] , &y[i] , &z[i]); scanf("%d%d%d" , &s , &t , &w); for(i = 1 ; i <= m ; i ++ ) if(z[i] < w) add(x[i] , y[i] , 1); while(bfs()) ans += dinic(s , 1 << 30); memset(head , 0 , sizeof(head)) , cnt = 1; for(i = 1 ; i <= m ; i ++ ) if(z[i] > w) add(x[i] , y[i] , 1); while(bfs()) ans += dinic(s , 1 << 30); printf("%d\n" , ans); return 0; }