1. 程式人生 > 其它 >[The 2021 ICPC Asia Shenyang Regional Contest](https://codeforces.com/gym/103427)

[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);
    }
}