启发式合并
推荐博客 : https://blog.csdn.net/zjznku/article/details/65937416
其实启发式合并核心思想就是将小的集合合并到大的集合上面
例题 : HDU 4358
求一颗树上以每个点为子树中权值出现 k 次的个数有多少个
#include <bits/stdc++.h> using namespace std; #define ll long long const int maxn = 1e5+5; int n, k; int a[maxn]; vector<int>ve[maxn], vv; int size[maxn], son[maxn]; int que[maxn]; void init(){ vv.clear(); for(int i = 1; i <= 100000; i++) ve[i].clear(); memset(size, 0, sizeof(size)); memset(son, 0, sizeof(son)); } void dfs1(int x){ //求出每个结点的重儿子 size[x] = 1; int num = 0; for(int i = 0; i < ve[x].size(); i++){ int to = ve[x][i]; dfs1(to); size[x] += size[to]; if (size[to] > num) {num = size[to]; son[x] = to;} } } int sum[maxn], ans[maxn]; int res = 0; bool vis[maxn]; void edit(int x, int sp){ //更新每个点的贡献 if (sum[a[x]] == k) res--; sum[a[x]] += sp; if (sum[a[x]] == k) res++; for(int i = 0; i < ve[x].size(); i++){ int to = ve[x][i]; if (vis[to]) continue; edit(to, sp); } } void dfs(int x, int sp){ //sp表示贡献是否被清空 for(int i = 0; i < ve[x].size(); i++){ //遍历所有非重儿子的结点 int to = ve[x][i]; if (to == son[x]) continue; dfs(to, 0); } if (son[x]) dfs(son[x], 1), vis[son[x]] = 1; edit(x, 1); //遍历x的所有结点 添加重儿子以外的贡献 ans[x] = res; //记录答案 if (son[x]) vis[son[x]] = 0; //重儿子的标记取消 if (!sp) edit(x, -1); //贡献取消 } int main() { //freopen("in.txt", "r", stdin); //freopen("out.txt", "w", stdout); int T; cin >> T; int x, y; int kas = 1; int sign = 0; while(T--) { if (sign) printf("\n"); init(); scanf("%d%d", &n, &k); for(int i = 1; i <= n; i++) { scanf("%d", &a[i]); vv.push_back(a[i]); } sort(vv.begin(), vv.end()); vv.erase(unique(vv.begin(), vv.end()), vv.end()); for(int i = 1; i <= n; i++) a[i] = lower_bound(vv.begin(), vv.end(), a[i])-vv.begin()+1; for(int i = 1; i < n; i++){ scanf("%d%d", &x, &y); ve[x].push_back(y); } dfs1(1); dfs(1, 0); int q; cin >> q; for(int i = 1; i <= q; i++) {scanf("%d", &que[i]);} printf("Case #%d:\n", kas++); for(int i = 1; i <= n; i++) printf("%d\n", ans[que[i]]); sign = 1; //for(int i = 1; i <= n; i++) printf("%d ", sum[i]); } return 0; }
东北日出西边雨 道是无情却有情