1. 程式人生 > 其它 >【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;		
}