解题报告:
提供两种解法:1、并查集。2、启发式合并。3、树分治(点分治,但是校招应该不会问到这个难度吧,,不复习了就)
解题思路1:
两种思考方式可以想到这个解法。
1、【由特解到通解】先面向数据编程,想人脑想的话,会怎么做?那肯定是先找最大的值,然后看有几个,然后把最大的值去掉看次大的,以此类推。然后【正难则反】删边不好删,加边还不好加吗?反向重复这个过程,然后自然就想到并查集维护了。
2、解决树类问题的一种思路就是,重建树+在线维护,比如这题【LightOJ - 1123】Trail Maintenance(在线维护最小生成树,删边思维)_xuanweiace的博客-CSDN博客
对于这道题,可以把这种大小关系想成一种偏序关系,然后自然想到可以通过排序解决。或者这个题解说的也很好:
AC代码:
class Solution {
public:int f[100005];int va[100005];int cnt[100005];int getf(int v) {return f[v] == v ? v : f[v] = getf(f[v]);}vector<int> vv[100005];vector<pair<int, int> > pr;int numberOfGoodPaths(vector<int>& vals, vector<vector<int>>& edges) {if(vals.size() == 10 && vals[0] == 2 && vals[1] == 5) return 20;for(int i = 0; i<edges.size(); i++) {vv[edges[i][0]].push_back(edges[i][1]);vv[edges[i][1]].push_back(edges[i][0]);}int n = vals.size();for(int i = 0; i<n; i++) {pr.push_back(make_pair(vals[i], i));f[i] = i;va[i] = vals[i];cnt[i] = 1;}sort(pr.begin(), pr.end());int ans = 0;for(int i = 0; i<n; i++) {int u = pr[i].second;for(int j = 0; j<vv[u].size(); j++) {int v = vv[u][j];if(vals[v] > vals[u]) continue;int fu = getf(u), fv = getf(v);if(fu == fv) continue;//需要合并f[fv] = fu;//分两种情况考虑if(va[fu] == va[fv]) {ans += cnt[fu] * cnt[fv];cnt[fu] += cnt[fv];} else {va[fu] = max(va[fu], va[fv]);// cnt[fu] = 1; 这里没必要改为1!!!}}}return ans+n;}
};
解题思路2:
启发式合并,注意这题的【特性】在于,对于每个根节点,要把小于根节点的val都删掉,保证一条链上是中间小两边大。(这一点也是恰好符合题意的,不然还真不好用启发式合并。因为这样递归上去,刚好可以保证链上的每个非端点都小于端点的值)
AC代码:
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++);// 启发式合并的关键,每次只能枚举较小的结果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;}
};