2021ICPC沈阳 H.Line Graph Matching(dfs树求割边+简单树形dp)

2021ICPC沈阳 H.Line Graph Matching(dfs树求割边+树形dp)

前言:思路感觉并不难想,主要是题目太难读懂。

题意:简化了就是在原图中选择相邻的两条边进行配对,配对后产生的贡献为两边权值之和,不能重复选边配对。

前置芝士:dfs树

题解:

1.首先可以比较容易的知道,如果有偶数条边肯定可以把所有的边都选上(可以用dfs树来简单证明,如果dfs树的一个节点回边的数量加上连向儿子的边的数量为偶数,那么就可以完美配对;如果为奇数,那么用上这个节点连上父亲的边,就可以完美匹配,若用了连向父亲的边,那么父亲节点在计算连向儿子的边的时候就要减一,这样递归上去,可以知道偶数条边一定可以全选)。

2.接下来讨论奇数边的情况,首先比较简单的来想这个问题,奇数条边的图,我们去掉一条边不就变成偶数条边的图了吗,就可以归到上面的情况了,所以只需要删掉一条权值最小的边不就行了吗。但是我们会发现,如果删掉的这条边是割边,那么会分成两个连通块,如果如果连通块的边的数量为奇数,那么肯定导致这个连通块有一条边不能完美匹配,所以我们删掉的边只能是两种边[1]非割边(删掉了之后形成了一个偶数条边的连通图)[2]割边中删掉了这条割边之后连通块边数为偶数的边,以上两种情况的边权取最小值为minn,输出答案sum-minn即可

代码

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
struct edge{
    
    
    int to;
    int id;
    ll w;
};
ll vis[200009];
vector<edge> g[200009];
int dp[200009];//用来求割边,详情请见博客上方连接
int bian[200009];//bian[i]表示子树中,以i为节点的子树有多少条边(子树节点连到i的祖先的边不算在内)
int dep[200009];
int n,m;
void dfs(int now,int fa){
    
    
    dep[now] = dep[fa] + 1;
    int id;
    for(auto j : g[now]){
    
    
        if(j.to == fa){
    
    
            id = j.id;
            continue;
        }
        if(dep[j.to]){
    
    
            if(dep[j.to] > dep[now]){
    
    
                dp[now]--;
            }else{
    
    
                dp[now]++;
                bian[j.to]++;//回边
            }
        }else{
    
    
            dfs(j.to,now);
            dp[now] += dp[j.to];
            bian[now] += (bian[j.to] + 1);//这个1指的是now连向儿子的边
        }
    }
    if(now != 1&&dp[now] == 0 && (bian[now]) % 2 == 1){
    
    //dp[now] == 0表示这个节点连向父亲的边为割边
        					//bian[now] % 2 == 1表示删掉连向父亲的边之后,其子树形成的连通图边的数量为奇数
        vis[id] = 2000000000;//设为无穷大表示不参与选最小的边的统计
    }
}
int main(){
    
    
    scanf("%d %d",&n,&m);
    ll sum = 0;
    for(int i = 1;i <= m;i++){
    
    
        int u,v;
        ll w;
        scanf("%d %d %lld",&u,&v,&w);
        sum += w;
        vis[i] = w;
        edge p;
        p.id = i;p.w = w;p.to = v;
        g[u].push_back(p);
        p.to = u;
        g[v].push_back(p);
    }
    if(m % 2 == 0){
    
    //偶数直接输出
        printf("%lld",sum);
        return 0;
    }
    dfs(1,0);
    ll minn = 2000000000;
    for(int i = 1;i <= m;i++){
    
    
        minn = min(vis[i],minn);
    }
    printf("%lld",sum-minn);
    return 0;
}

猜你喜欢

转载自blog.csdn.net/m0_51687577/article/details/126778140