[The 2021 ICPC Asia Shenyang Regional Contest](https://codeforces.com/gym/103427)
H. Line Graph Matching
題意
給出一個無向連通圖\(G\),定義其線圖\(L(G)\)為:
線圖中的每個點對應原圖中的一條邊
線圖中的兩點之間有邊,當且僅當這兩點對應的原圖中的兩條邊有公共端點,且線圖中的這條邊的邊權為原圖中那兩條邊的邊權和。
求\(L(G)\)的最大權匹配。
\(3\le n \le 10^5, n-1 \le m \le min(\frac{n(n-1)}{2}, 2\times 10^5)\)
分析
線圖中的兩點一邊 對應原圖中的兩邊一點 並且原圖中的一個度數為\(d\)的點對應線圖中的\(d\choose 2\)條邊
選出的匹配要滿足沒有公共點 即在原圖中沒有公共邊
問題轉化為在原圖中選出若干對有相鄰點的邊,使得它們之間沒有公共邊,並且選出的邊邊權和最大。
由於邊權都非負,每條邊最多被選一次,並且圖連通, 因此若有偶數條邊,全選就完事了
若有奇數條邊 則有至少一條邊不能被選 為了讓選出邊的總和最大,我們考慮邊權最小的那條邊
如果邊權最小的邊不是割邊,不選它可以使得剩下的邊全部被選,答案最優
如果邊權最小的邊是割邊,不選它會導致產生兩個新的連通塊,這兩個連通塊內的邊數有2種情況:
1.均有奇數條邊
2.均有偶數條邊
對於情況1,不選這條割邊會導致剩下的兩個連通塊內都要刪去至少一條邊,總刪去邊權>=這條邊邊權+兩邊連通塊各自最小邊權
然鵝,情況1可以把這條割邊劃給兩個連通塊中的某一個,使得這個連通塊中的邊及這條割邊可以被全選,然後在另外一個連通塊中不選某一條邊。這樣的總刪去邊權=某個連通塊內的刪去邊權,優於不選這條割邊的答案
因此,連線兩個有奇數條邊的連通塊的割邊必定被選,即使它不是當前連通塊內最小邊權的邊
對於情況2,剩下的邊有偶數條,可以全選,刪去割邊是正確的。
總結:連線兩個奇數條邊的連通塊的割邊必選 找到這樣的割邊標記一下,然後在剩下的邊裡找邊權最小的那個即可
實現
需要統計刪去一條邊後,兩端的連通塊內的邊數
根據子樹內度數和計運算元樹內的邊數
#include<bits/stdc++.h> using namespace std; const int maxn = 3e5 + 7, maxm = 4e5+7; #define ll long long const ll inf = 0x7f7f7f7f7f7f7f7fLL; struct edge { int v, nxt; ll w; }e[maxm]; int head[maxn], eid, n, m, dfn[maxn], low[maxn], idfn[maxn], tot; ll deg[maxn], sumw; bool bridge[maxm]; void init() { memset(head, -1, sizeof(head)); eid = 0; } void insert(int u, int v, ll w) { e[eid].v = v; e[eid].w = w; e[eid].nxt = head[u]; head[u] = eid++; deg[v]++; } void tarjan(int u, int in_edge) { dfn[u] = low[u] = ++tot; idfn[tot] = dfn[u]; for (int i = head[u]; ~i; i = e[i].nxt) { int v = e[i].v; if (!dfn[v]) { tarjan(v, i); deg[u] += deg[v]; //子樹內度數 low[u] = min(low[u], low[v]); if (low[v] > dfn[u]) { bridge[i] = bridge[i ^ 1] = true; } } else if (i != (in_edge ^ 1)) low[u] = min(low[u], dfn[v]); } } int rd() { int s = 0; char c = getchar(); while (c < '0' || c > '9'){c = getchar();} while (c >= '0' && c <= '9'){ s = s * 10 + c - '0'; c = getchar(); } return s; } int main() { n = rd(); m = rd(); init(); for (int i = 1, u, v, w; i <= m; i++) { u = rd(); v = rd(); w = rd(); insert(u, v, w); insert(v, u, w); sumw += w; } tarjan(1, -1); if (deg[1]/2ll % 2ll == 0) { /*for (int i = 1; i <= n; i++) { printf("deg[%d] == %lld\n", i, deg[i]); }*/ printf("%lld\n", sumw); return 0; } else{ ll ans = 0; for (int ed = 0, u, v; ed < eid; ed += 2) { u = e[ed^1].v; v = e[ed].v; if (deg[u] < deg[v]) swap(u, v); if (!bridge[ed] || (((deg[1] - deg[v] - 1) / 2) % 2ll == 0 && ((deg[v]-1ll)/2ll) % 2 == 0)) { ans = max(ans, sumw - e[ed].w); } else { continue; } } printf("%lld\n", ans); } }