【PR #1 B】守衛(網路流)
守衛
題目連結:PR #1 B
題目大意
給你一個無向圖,然後每個邊有建的費用。
然後有若干個人,每個人有一些存在的點可以選擇。
要你每個人分配一個對應的位置存在,然後建一些邊,使得每個點恰好可以被一個人通過邊得到,而且選的邊的邊權和最小。
思路
首先是一個性質就是其實你可以根本不用管“恰好”這個性質。
然後這個性質挺顯然的,就如果你可以連通塊裡面有兩個人的話你完全可以選它們路徑上的費用最大的邊斷開來優化費用。
然後接著有一個性質是你可以直接用最小生成森林來搞。
因為你考慮 \(x-y-z\) 這個東西,有一條 \(x-z\) 不選,那如果它會用到的話(比如 \(z\) 要到 \(x\)),那你完全可以從 \(z-y-x\)
而且如果 \(y\) 有別的地方可以到的話,那你就沒有必要 \(z\) 到 \(z\) 了,直接從 \(y\) 到 \(x\) 就可以了,怎麼看費用都是優的。
然後我們其實可以考慮反著來,考慮能不能刪去一條邊。
然後如果每個人的點固定不難得到一個樹形 DP,就是當前點的子樹,然後當前點的連通塊是否有人。
然後你會發現一個人的作用就是處理最上面的一部分或者讓它祖先上的某條邊可以斷開。
那我們就是要找一些邊(不能重複)使得減去的費用最多。
那不難想到一個網路流的方法,每個人有一個點,向可以走的地方連邊。
然後我們並查集記錄連通塊之類的話我們就可以把邊連起來的時候開一個新點表示這條邊,然後用這個點作為它們它們兩個的父親,然後這個父親向匯點連邊(有費用的,費用就是邊權)表示這條邊被割了。
然後最後並查集的根的位置我們就要處理它的連通塊,所以我們就也向匯點鄰邊,就沒有費用了
然後上面邊的流量為 \(1\)
當然我們要保證流量就是 \(k\),也就是每個點都會起作用。
然後我們這個最大費用可以把有費用的地方 \(x\) 變成 \(inf-x\) 然後就正常最小費用最大流做。
然後優化的值可能是負的是因為沒有每個連通塊都有點,那直接判 \(-1\) 就可以了。
然後這題可以繼續找一個性質(就是按邊權從大到小依次嘗試刪除能刪就刪是最優的),然後就可以用類似匈牙利的演算法來搞,但是感覺不如網路流好寫。
程式碼
#include<queue> #include<cstdio> #include<iostream> #include<algorithm> #define ll long long #define INF 0x3f3f3f3f3f3f3f3f using namespace std; const int N = 300 + 10; struct Line { int x, y, w; }E[N * N]; int n, m, k, fa[N << 1], sn, x; const ll inf = 1e9; ll ans; bool cmp(Line x, Line y) { return x.w < y.w; } int find(int now) { if (fa[now] == now) return now; return fa[now] = find(fa[now]); } struct WLL { struct node { ll x, val; int to, nxt, op; }e[(N * N) << 2]; int tot, S, T, deg[N << 2], le[N << 2], KK, lee[N << 2]; ll dis[N << 2]; bool in[N << 2]; queue <int> q; void add(int x, int y, ll z, ll va) { e[++KK] = (node){z, va, y, le[x], KK + 1}; le[x] = KK; e[++KK] = (node){0, -va, x, le[y], KK - 1}; le[y] = KK; } bool SPFA() { while (!q.empty()) q.pop(); for (int i = 1; i <= tot; i++) deg[i] = 0, dis[i] = INF, in[i] = 0, lee[i] = le[i]; q.push(S); deg[S] = 1; dis[S] = 0; in[S] = 0; while (!q.empty()) { int now = q.front(); q.pop(); for (int i = le[now]; i; i = e[i].nxt) if (dis[e[i].to] > dis[now] + e[i].val && e[i].x) { deg[e[i].to] = deg[now] + 1; dis[e[i].to] = dis[now] + e[i].val; if (!in[e[i].to]) { in[e[i].to] = 1; q.push(e[i].to); } } in[now] = 0; } ll tmp = INF; return dis[T] < tmp; } int dfs(int now, int sum) { if (now == T) return sum; int go = 0; for (int &i = lee[now]; i; i = e[i].nxt) if (deg[e[i].to] == deg[now] + 1 && dis[e[i].to] == dis[now] + e[i].val && e[i].x) { int this_go = dfs(e[i].to, min(1ll * sum - go, e[i].x)); if (this_go) { e[i].x -= this_go; e[e[i].op].x += this_go; go += this_go; if (go == sum) return go; } } if (go != sum) deg[now] = -1; return go; } pair <ll, int> dinic() { pair <ll, int> re = make_pair(0ll, 0); while (SPFA()) { int x = dfs(S, INF); re.first += dis[T] * x; re.second += x; } return re; } }W; int main() { scanf("%d %d %d", &n, &m, &k); for (int i = 1; i <= m; i++) { scanf("%d %d %d", &E[i].x, &E[i].y, &E[i].w); } sort(E + 1, E + m + 1, cmp); W.tot = 2 * n + k; W.S = ++W.tot; W.T = ++W.tot; int tot = n; for (int i = 1; i < 2 * n; i++) fa[i] = i; for (int i = 1; i <= m; i++) { int x = find(E[i].x), y = find(E[i].y); if (x != y) { fa[x] = fa[y] = ++tot; W.add(x, tot, 1, 0); W.add(y, tot, 1, 0); W.add(tot, W.T, 1, inf - E[i].w); ans += E[i].w; } } int num = k; for (int i = 1; i <= tot; i++) if (find(i) == i) W.add(i, W.T, 1, 0), num--; for (int i = 1; i <= k; i++) { scanf("%d", &sn); W.add(W.S, 2 * n + i, 1, 0); for (int j = 1; j <= sn; j++) { scanf("%d", &x); W.add(2 * n + i, x, 1, 0); } } pair <ll, int> re = W.dinic(); if (re.second != k) {printf("-1"); return 0;} ll va = inf * num - re.first; if (va < 0) {printf("-1"); return 0;//這個會成立是因為可能一個連通塊裡面沒有人可以到 ans -= va; printf("%lld", ans); return 0; }