【PR #1】守衛
阿新 • • 發佈:2022-04-05
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 重構樹:
- 只要非葉子節點的子樹存在一個守衛,並且剩下的非子樹部分存在一個守衛,那麼就可以刪去這個點代表的邊。
費用流
這個問題可以利用費用流求解:
這裡的流量可以理解為子樹內的守衛個數。
-
首先每個點連向父親,費用 \(0\),容量 \(1\)。
-
為了確保整個連通塊都能到一個守衛,強制在根流,在根節點向匯點連費用 \(INF\), 容量 \(1\)的邊。
-
對於每個非葉子節點,可以向匯點連費用 \(w_e\), 容量 \(1\) 的邊,表示可以斷掉這條邊。
-
對於每個守衛,源點向它連費用為 \(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; }