好路径的数目(并查集,启发式)

题意

给定一颗树,有点权,问好路径的数目(unordered_pair)。
好路径定义:

  • 开始节点和结束节点点权相同
  • 开始节点权值为路径点权max

constrains:
1 ≤ n ≤ 3 e 4 1\leq n\leq 3e4 1n3e4
1 ≤ v a l i ≤ 1 e 5 1\leq val_i\leq 1e5 1vali1e5

思路

考虑按点权从小到大重新构建这棵树(可能见过这种思维,但比赛中完全没考虑),已插入的点之间连边,需要维护并查集,以及该连通块中含有当前最大权值的点的个数,从而更新答案。
启发式合并也能做。

代码

class Solution {
public:
    map<int, vector<int>> G;
    vector<int> va;
    vector<int> ad[30010];
    int f[30010];
    map<int,int> cnt;
    bool c[30010];   
    int n,ans;
    int findfa(int x){
        return x==f[x]?x:f[x]=findfa(f[x]);
    }
    void merge(int u,int v){
        u = findfa(u);v = findfa(v);
        if(u!=v){
            f[v] = u;
            ans+=cnt[u]*cnt[v];
            cnt[u] += cnt[v];
        }
    }
    
    int numberOfGoodPaths(vector<int>& vals, vector<vector<int>>& edges) {
        va = vals;
        ans = 0;
        n = vals.size();
        for(int i = 0;i<vals.size();i++){
            G[vals[i]].push_back(i);
        }
        for(int i = 0;i<n;i++) f[i] = i;
        for(auto s:edges){
            ad[s[0]].push_back(s[1]);
            ad[s[1]].push_back(s[0]);
        }
        for(auto [a,b]:G){
            cnt.clear();
            for(auto u:b){
                c[u] = 1;
                cnt[u]++;
                for(auto v:ad[u]){
                    if(!c[v]) continue;
                    merge(u,v);
                }
            }
        }
        return ans+n;
    }
};

启发式合并

class Solution {
    typedef pair<int, int> pii;

    vector<int> A;
    long long ans = 0;
    vector<vector<int>> e;

    map<int, int> dfs(int sn, int fa) {
        // 返回的 map 里一开始只有子树的根
        map<int, int> ret;
        ret[A[sn]]++;

        for (int fn : e[sn]) if (fn != fa) {
            // 递归调用
            map<int, int> tmp = dfs(fn, sn);
            // 清除子节点里权值不符合要求的路径。
            // 这里好像枚举了 tmp,破坏了启发式合并的复杂度,
            // 但由于我们枚举到的每个元素都会被删掉,所以总共最多只有 n 次删除,复杂度还是正确的。
            for (auto it = tmp.begin(); it != tmp.end() && it->first < A[sn]; ) tmp.erase(it++);
            // 启发式合并的关键,每次只能枚举较小的结果(复杂度的原理等同于轻重链剖分https://oi-wiki.org/graph/dsu-on-tree/)
            if (tmp.size() > ret.size()) swap(tmp, ret);
            // 统计路径中深度最小的点为 sn 的好路径数量
            for (auto it = tmp.begin(); it != tmp.end(); it++)
                if (ret.count(it->first)) ans += (it->second) * ret[it->first];
            // 更新返回值
            for (auto it = tmp.begin(); it != tmp.end(); it++) ret[it->first] += it->second;
        }

        return ret;
    }

public:
    int numberOfGoodPaths(vector<int>& vals, vector<vector<int>>& edges) {
        int n = vals.size();
        A = vals;
        e.resize(n);
        for (auto &vec : edges) {
            e[vec[0]].push_back(vec[1]);
            e[vec[1]].push_back(vec[0]);
        }

        dfs(0, 0);
        return ans + n;
    }
};

作者:TsReaper
链接:https://leetcode.cn/circle/discuss/f2jB1Y/view/b4qNEp/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值