• CF1120D Power Tree


    Solution Part1

    子树操作,可以用DFS序解决。
    这一题,我们实际上只用将叶子节点按DFS序从小到大排列,将其编号为1...n(n为叶子数量)。
    对于点u,它控制的区间就是[cnt + 1,cnt + siz[u] + 1],cnt表示比u的DFS序小的叶子的数量,siz[u]表示u的子节点中的叶子数量。
    我们将这个序列差分,对于每个区间[a,b],在点a与点b + 1之间连一条边,权值为控制[a,b]的代价,数字可以通过这条边流向另一个点,一端数字加w,另一端数字减w。
    因为有的区间右端点可能为n,所以我们要多增加一个点n + 1,点n + 1的权值恒为0。这也意味着差分数组和为0。
    这样我们的问题就变成了,在一个有n + 1个点的图上,选的边可以通过“搬运”两端的数字将图清零,求选边的最小代价。
    很明显,只要图连通,数字就可以在图上自由流动,不管如何规定权值都可以将图清零。
    求一遍最小生成树即可。

    Solution Part2

    第二行要我们输出可能存在于最优集合中的点,也就是求出所有最优集合的并集。
    我们将其转化为求图上所有最小生成树的边集的并集。
    我们考虑Kruskal算法。
    Kruskal算法通过“合并”点集来求最小生成树。
    我们将“合并”在一起的点通过类似强连通缩点的形式将其缩为一个点。此时图中所有点都属于不同的集合(因为缩点了)。若此时图中最小权值为w,此时所有权值为w的边都属于该图的某一个最小生成树。

    Code

    #include <bits/stdc++.h>
    
    using namespace std;
    
    const int MAXN = 2e5 + 10;
    
    struct EDGE{
        int u,v,w,id;
    }e[MAXN];
    
    bool cmp(EDGE a,EDGE b) {
        return a.w < b.w;
    }
    
    vector<int> edge[MAXN];
    int wei[MAXN],n,siz[MAXN],fa[MAXN],num[MAXN],cnt,lcnt,ncnt;
    bool mark[MAXN];
    long long ans;
    
    int find(int x) {return x == fa[x]?x:fa[x] = find(fa[x]);}
    
    void merge(int a,int b) {fa[find(a)] = find(b);return;}
    
    void DFS(int u,int pre) {
        int len = edge[u].size();
        num[u] = ++cnt;
        if (len == 1 && u != 1) {
            lcnt++;
            e[num[u]] = {lcnt,lcnt + 1,wei[u],u};
            siz[u] = 1;
            return;
        }
        e[num[u]].u = lcnt + 1;
        for (int i = 0; i < len; i++) {
            int v = edge[u][i];
            if (v == pre) continue;
            DFS(v,u);
            siz[u] += siz[v];
        }
        e[num[u]].v = e[num[u]].u + siz[u];
        e[num[u]].w = wei[u];
        e[num[u]].id = u;
    }
    
    void Kruskal() {
        sort(e + 1,e + n + 1,cmp);
        for (int i = 1; i <= lcnt + 1; i++) fa[i] = i;
        int j = 1;
        for (int i = 1; i <= n; i = j + 1) {
            while (j + 1 <= n && e[j + 1].w == e[i].w) j++;
            for (int k = i; k <= j; k++) {
                if (find(e[k].u) != find(e[k].v)) mark[e[k].id] = true,ncnt++;
            }
            while (i <= j) {
                int a = e[i].u,b = e[i].v;
                if (find(a) != find(b)) {
                    merge(a,b);
                    ans += e[i].w;
                }
                i++;
            }
        }
        return;
    }
    
    int main() {
        scanf("%d",&n);
        for (int i = 1; i <= n; i++) {
            scanf("%d",&wei[i]);
        }
        for (int i = 1; i < n; i++) {
            int u,v;
            scanf("%d%d",&u,&v);
            edge[u].push_back(v);
            edge[v].push_back(u);
        }
        DFS(1,0);
        Kruskal();
        cout << ans << ' ' << ncnt << endl;
        for (int i = 1; i <= n; i++) {
            if (mark[i]) cout << i << ' ';
        }
        cout << endl;
        return 0;
    }
    

    注:判断叶子节点时也要判它是不是根,不然遇到只有两个点的树就挂了。

  • 相关阅读:
    高德地图API,获取和设置zoom级别和中心点
    高德地图API注册使用教程简答演示
    HTML5 视频流行插件之video.js
    audio实现自定义音频播放器
    HTML5 audio API事件
    HTML5之audio属性
    GitLab的安装及使用教程
    设计模式六大原则(PHP)
    面向对象设计
    自定义的异常类
  • 原文地址:https://www.cnblogs.com/kjd123456/p/15125647.html
Copyright © 2020-2023  润新知