其他分享
首页 > 其他分享> > 【PR #1】守卫

【PR #1】守卫

作者:互联网

Public Round #1 T2
题目来源:2021-2022 ICPC North America Championships. Problem I.

题意:

有 \(n(\leq 300)\) 个点 \(m\) 条边的无向图,每条边有边权。

有 \(k (1 \leq k \leq n)\) 个守卫,每个守卫必须放在 \(S_i\) 个给出点集中的一个,原图中每个点最多放一个守卫。

可以选择连一些边,要满足连上这些边后任意点都能到达任意一个守卫,求最小花费。

kruskal重构树

首先,选择连的边在最小生成森林上,否则可以利用树边替换非树边,方案不劣。

关于连通性,可以想到 kruskal 重构树:

费用流

这个问题可以利用费用流求解:

这里的流量可以理解为子树内的守卫个数。

  1. 首先每个点连向父亲,费用 \(0\),容量 \(1\)。

  2. 为了确保整个连通块都能到一个守卫,强制在根流,在根节点向汇点连费用 \(INF\), 容量 \(1\)的边。

  3. 对于每个非叶子节点,可以向汇点连费用 \(w_e\), 容量 \(1\) 的边,表示可以断掉这条边。

  4. 对于每个守卫,源点向它连费用为 \(0\), 容量为 \(1\) 的边;也可以向它能待的点连费用为 \(0\), 容量为 \(1\) 的边 。

跑最大费用最大流,这样优先断深度浅的点代表的边,也就是说流出流量的点是一个包含根的连通块(应该是吧),这样就能保证正确性。

能断掉的最大的边权和就是 \(\text{maxcost} - cnt \times INF\), \(cnt\) 表示连通块个数。

#include<bits/stdc++.h>
using namespace std;

using ll = long long;
using ull = unsigned long long; 
const int MAXN = 300010;
const int MAXM = 400010;
const int INF = 0x3f3f3f3f;
const int mod = 1000000007;
//const int mod = 998244353;
//int mod; 	
const double eps = 1e-5; 

template <typename T>
void Read(T &x) {
	x = 0; T f = 1; char a = getchar();
	for(; a < '0' || '9' < a; a = getchar()) if (a == '-') f = -f;
	for(; '0' <= a && a <= '9'; a = getchar()) x = (x * 10) + (a ^ 48);
	x *= f;
}

inline int add(const int &a, const int &b, const int p = mod) {
	static int c;
	c = a + b;
	if (c >= p) c -= p;
	return c; 
} 
inline int dec(const int &a, const int &b, const int p = mod) {
	static int c;
	c = a - b;
	if (c < 0) c += p;
	return c; 
} 
inline int mul(const int &a, const int &b, const int p = mod) {
	return 1ll * a * b % p; 
}
inline int qpow(int a, ll b, const int p = mod) {
	int sum(1);
	if (b <= 0) return 1;
	while(b) {
		if (b & 1) sum = mul(sum, a, p);
		a = mul(a, a, p);
		b >>= 1; 
	}
	return sum; 
}

int n, m, k; 

struct Edge {
	int u, v, w; 
} E[MAXM];

int fa[MAXN]; 
int getfa(int x) {
	return fa[x] = fa[x] == x ? x : getfa(fa[x]); 
}

int s, t; 
struct edge {
	int next, len, point, cost; 
} e[MAXM];
int first[MAXN]; 
int cnt = 1; 
void add(int u, int v, int w, int c) {
	++ cnt; 
	e[cnt].point = v;
	e[cnt].len = w;
	e[cnt].cost = c;
	e[cnt].next = first[u]; 
	first[u] = cnt; 
}
void Add(int u, int v, int w, int c) {
	add(u, v, w, c);
	add(v, u, 0, -c); 
}

bool vis[MAXN]; 
int dis[MAXN], cur[MAXN];
bool dijkstra() {
	for (int i = 1; i <= t; i ++)
		dis[i] = -INF, 
		vis[i] = 0; 
	memcpy(cur, first, sizeof(first)); 
	priority_queue<pair<int, int> > q;
	dis[s] = 0; 
	q.push(make_pair(dis[s], s));
	while (!q.empty()) {
		int u = q.top().second; q.pop();
		if (vis[u]) continue;
		vis[u] = 1; 
		for (int i = first[u]; i; i = e[i].next) {
			int v = e[i].point;
			if (e[i].len && dis[v] < dis[u] + e[i].cost) {
				dis[v] = dis[u] + e[i].cost; 
				q.push(make_pair(dis[v], v)); 
			}
		}
	}
	return dis[t] != -INF; 
}
int maxcost; 
int dfs(int u, int flow) {
	if (u == t)
		return flow;
	int sum = 0;
	vis[u] = 1; 
	for (int &i = cur[u]; i; i = e[i].next) {
		int v = e[i].point;
		if (vis[v] || !e[i].len || dis[v] != dis[u] + e[i].cost) continue; 
		int out = dfs(v, min(flow, e[i].len));
		sum += out, flow -= out; 
		e[i].len -= out, e[i ^ 1].len += out;
		maxcost += out * e[i].cost; 
		if (!flow) break; 
	}
	return sum; 
}
int dinic() {
	int maxflow = 0;
	while (dijkstra()) {
		for (int i = 1; i <= t; i ++)
			vis[i] = 0;
		maxflow += dfs(s, INF); 
	}	 
	return maxflow; 
}

int main() {
	ios::sync_with_stdio(false);
	cin.tie(0); 
	cin >> n >> m >> k; 
	for (int i = 1; i <= m; i ++)
		cin >> E[i].u >> E[i].v >> E[i].w; 
	sort(E + 1, E + m + 1, [](Edge x, Edge y) {
		return x.w < y.w; 
	}); 
	iota(fa + 1, fa + n + 1, 1); 
	s = 2 * n + k + 1, t = 2 * n + k + 2; 
	int cnt = n, node = n, ans = 0; 
	for (int i = 1; i <= m; i ++) {
		int x = getfa(E[i].u), y = getfa(E[i].v); 
		if (x == y) continue;
		int z = ++ node;
		fa[x] = fa[y] = fa[z] = z;  
		ans += E[i].w;
		cnt --; 
		Add(x, z, 1, 0);
		Add(y, z, 1, 0);
		Add(z, t, 1, E[i].w);  
	}
	for (int i = 1; i <= node; i ++)
		if (fa[i] == i)
			Add(i, t, 1, 1001);
	for (int i = 1; i <= k; i ++) {
		int len;
		cin >> len; 
		int u = 2 * n + i; 
		Add(s, u, 1, 0); 
		for (int j = 1; j <= len; j ++) {
			int v; 
			cin >> v;
			Add(u, v, 1, 0); 
		}
	}
	int maxflow = dinic(), d = maxcost - cnt * 1001; 
	if (maxflow < k || d > ans || d < 0) return cout << -1, 0; 
	cout << ans - d; 
	return 0;		
} 

标签:PR,cnt,const,int,len,守卫,return,dis
来源: https://www.cnblogs.com/qjbqjb/p/16103859.html