*【学习笔记】(19) 启发式合并
启发式合并
启发式合并核心思想就一句话:把小集合的合并到大的里。
启发式合并思想可以放到很多数据结构里,链表、线段树、甚至平衡树都可以。
考虑时间复杂度,设总共有
dsu on tree 就是树上启发式合并。
dsu on tree 优雅的思想
对于以 u 为根的子树 ①. 先统计它轻子树(轻儿子为根的子树)的答案,统计完后删除信息 ②. 再统计它重子树(重儿子为根的子树)的答案 ,统计完后保留信息 ③. 然后再将重子树的信息合并到 u上 ④. 再去遍历 u 的轻子树,然后把轻子树的信息合并到 u 上 ⑤. 判断 u 的信息是否需要传递给它的父节点(u 是否是它父节点的重儿子)
dsu on tree 暴力的操作体现于统计答案上(不同的题目统计方式不一样)
例题
Ⅰ.CF570D Tree Requests
重排之后能否构成回文串,即判断出现次数为奇数的字符个数是否小于等于1
因为要多次询问以某个点为根深度为
所以可以对每个节点开个 vector,再把和它有关的询问保存在vector
然后在 dsu on tree 的过程进行到该节点时遍历这个节点的 vector (相关询问)
#include<bits/stdc++.h> #define pb push_back #define mp make_pair using namespace std; const int N = 1e6 + 67; int read(){ int x = 0, f = 1; char ch = getchar(); while(ch < '0' || ch > '9'){if(ch == '-') f = -f; ch = getchar();} while(ch >= '0' && ch <= '9'){x = (x << 1) + (x << 3) + (ch ^ 48); ch = getchar();} return x * f; } int n, m, tot; int Head[N], to[N], Next[N]; int a[N], son[N], sz[N], d[N]; vector<pair<int, int> > Q[N]; bool vis[N]; int b[N][26], ct[N], ans[N]; void add(int u, int v){ to[++tot] = v, Next[tot] = Head[u], Head[u] = tot; } void dfs1(int x, int fa){ d[x] = d[fa] + 1, sz[x] = 1; for(int i = Head[x]; i; i = Next[i]){ int y = to[i]; if(y == fa) continue; dfs1(y, x), sz[x] += sz[y]; if(sz[y] > sz[son[x]]) son[x] = y; } } void calc(int x, int fa){ if(b[d[x]][a[x]]) ct[d[x]]--; else ct[d[x]]++; b[d[x]][a[x]] ^= 1; for(int i = Head[x]; i; i = Next[i]){ int y = to[i]; if(y == fa || vis[y]) continue; calc(y, x); } } void dfs2(int x, int fa, int opt){ for(int i = Head[x]; i; i = Next[i]){ int y = to[i]; if(y == fa || y == son[x]) continue; dfs2(y, x, 0); } if(son[x]) dfs2(son[x], x, 1), vis[son[x]] = 1; calc(x, fa), vis[son[x]] = 0; for(auto it : Q[x]) ans[it.second] = (ct[it.first] <= 1); if(!opt) calc(x, fa); } int main(){ n = read(), m = read(); for(int i = 2; i <= n; ++i){ int u = read(); add(u, i), add(i, u); } for(int i = 1; i <= n; ++i) { char ch; cin >> ch; a[i] = ch - 'a'; } for(int i = 1; i <= m; ++i){ int u = read(), k = read(); Q[u].pb(mp(k, i)); } dfs1(1, 0), dfs2(1, 0, 0); for(int i = 1; i <= m; ++i) printf("%s\n", ans[i] ? "Yes" : "No"); return 0; }
Ⅱ.CF375D Tree and Queries
开个数组
#include<bits/stdc++.h> #define pb push_back #define mp make_pair #define pii pair<int,int> using namespace std; const int N = 2e5 + 67; int read(){ int x = 0, f = 1; char ch = getchar(); while(ch < '0' || ch > '9'){if(ch == '-') f = -f; ch = getchar();} while(ch >= '0' && ch <= '9'){x = (x << 1) + (x << 3) + (ch ^ 48); ch = getchar();} return x * f; } int n, m, tot; int fa[N], sz[N], son[N]; int c[N], cnt[N], ans[N], f[N]; int Head[N], to[N], Next[N]; vector<pii> Q[N]; bool vis[N]; void add(int u, int v){ to[++tot] = v, Next[tot] = Head[u], Head[u] = tot; } void dfs1(int x, int f){ fa[x] = f, sz[x] = 1; for(int i = Head[x]; i; i = Next[i]){ int y = to[i]; if(y == fa[x]) continue; dfs1(y, x); sz[x] += sz[y]; if(sz[y] > sz[son[x]]) son[x] = y; } } void calc(int x, int val){ if(val == 1) f[cnt[c[x]] += val]++; else --f[cnt[c[x]]], cnt[c[x]] += val; for(int i = Head[x]; i; i = Next[i]){ int y = to[i]; if(y == fa[x] || vis[y]) continue; calc(y, val); } } void dfs2(int x, int opt){ for(int i = Head[x]; i; i = Next[i]){ int y = to[i]; if(y == fa[x] || y == son[x]) continue; dfs2(y, 0); } if(son[x]) dfs2(son[x], 1), vis[son[x]] = 1; calc(x, 1), vis[son[x]] = 0; for(auto it : Q[x]) ans[it.second] = f[it.first]; if(!opt) calc(x, -1); } int main(){ n = read(), m = read(); for(int i = 1; i <= n; ++i) c[i] = read(); for(int i = 1; i < n; ++i){ int u = read(), v = read(); add(u, v), add(v, u); } for(int i = 1; i <= m; ++i){ int u = read(), k = read(); Q[u].pb(mp(k, i)); } dfs1(1, 0), dfs2(1, 0); for(int i = 1; i <= m; ++i) printf("%d\n", ans[i]); return 0; }
Ⅲ. P4149 [IOI2011] Race
树上两点
那么问题就转换成在树上找到两点
于是我们可以定义
因为我们计算两点的简单路径权值和、两点简单路径的边的个数是根据它们的lca ,所以一个分支内的节点不能相互影响
所以需要先对一个分支统计完贡献后,再添加它的信息
#include<bits/stdc++.h> #define pb push_back #define mp make_pair #define pii pair<int,int> #define int long long using namespace std; const int N = 5e5 + 67; int read(){ int x = 0, f = 1; char ch = getchar(); while(ch < '0' || ch > '9'){if(ch == '-') f = -f; ch = getchar();} while(ch >= '0' && ch <= '9'){x = (x << 1) + (x << 3) + (ch ^ 48); ch = getchar();} return x * f; } int n, m, k, tot, ans = 0x3f3f3f3f3f3f3f3f; int fa[N], sz[N], son[N], d[N], dis[N]; int Head[N], to[N], Next[N], edge[N]; map<int, int> minn; void add(int u, int v, int w){ to[++tot] = v, Next[tot] = Head[u], Head[u] = tot, edge[tot] = w; } void dfs1(int x, int f){ fa[x] = f, sz[x] = 1, d[x] = d[f] + 1; for(int i = Head[x]; i; i = Next[i]){ int y = to[i]; if(y == fa[x]) continue; dis[y] = dis[x] + edge[i], dfs1(y, x), sz[x] += sz[y]; if(sz[y] > sz[son[x]]) son[x] = y; } } void calc(int x, int rt){ int s = k + 2 * dis[rt] - dis[x]; if(minn.count(s)) ans = min(ans, minn[s] + d[x] - d[rt] * 2); for(int i = Head[x]; i; i = Next[i]){ int y = to[i]; if(y == fa[x]) continue; calc(y, rt); } } void change(int x){ if(minn.count(dis[x])) minn[dis[x]] = min(minn[dis[x]], d[x]); else minn[dis[x]] = d[x]; for(int i = Head[x]; i; i = Next[i]){ int y = to[i]; if(y == fa[x]) continue; change(y); } } void dfs2(int x, int opt){ for(int i = Head[x]; i; i = Next[i]){ int y = to[i]; if(y == fa[x] || y == son[x]) continue; dfs2(y, 0); } if(son[x]) dfs2(son[x], 1); int s = k + dis[x]; if(minn.count(s)) ans = min(ans, minn[s] - d[x]);minn[dis[x]] = d[x]; for(int i = Head[x]; i; i = Next[i]){ int y = to[i]; if(y == fa[x] || y == son[x]) continue; calc(y, x), change(y); } if(!opt) minn.clear(); } signed main(){ n = read(), k = read(); for(int i = 1; i < n; ++ i){ int u = read() + 1, v = read() + 1, w = read(); add(u, v, w), add(v, u, w); } dfs1(1, 0), dfs2(1, 0); printf("%lld\n", ans == 0x3f3f3f3f3f3f3f3f ? -1 : ans); return 0; }
Ⅳ.CF741D Arpa’s letter-marked tree and Mehrdad’s Dokhtar-kosh paths
1.重排后构成回文的条件为:
①.每个字母出现的次数都为偶数 ②.一个字母出现次数为奇数,其余字母出现次数为偶数
2.字母的范围为 a ~ z , 把其转换成二进制状态(偶数为0,奇数为1)
那么满足回文条件的二进制为 : 00...000 , 00..001 , 00..010 , ... , 10..000
3.维护一个从根节点到子节点u的前缀异或和数组 X
那么 u 到 v 的简单路径的字母重排后的二进制形式为 X[u] ^ X[v]
4.节点 u 的答案有三种可能:
①.它的两个不同分支的节点构成的简单路径 ②.它的一个分支到它本身构成的简单路径 ③.它的子节点的 ans
定义
#include<bits/stdc++.h> #define pb push_back #define pii pair<int, int> #define mp make_pair using namespace std; const int N = 5e5 + 67; int read(){ int x = 0, f = 1; char ch = getchar(); while(ch < '0' || ch > '9'){if(ch == '-') f = -f; ch = getchar();} while(ch >= '0' && ch <= '9'){x = (x << 1) + (x << 3) + (ch ^ 48); ch = getchar();} return x * f; } bool _u; int n, tot, maxn, XQ; int hd[N], to[N << 1], nxt[N << 1]; int son[N], sz[N], dep[N], X[N]; int a[N], f[(1 << 22) + 67], ans[N]; void add(int u, int v){ to[++tot] = v, nxt[tot] = hd[u], hd[u] = tot; } void dfs1(int x, int ff, int cur){ dep[x] = dep[ff] + 1, sz[x] = 1; X[x] = cur ^ a[x]; for(int i = hd[x]; i; i = nxt[i]){ int y = to[i]; if(y == ff) continue; dfs1(y, x, X[x]); sz[x] += sz[y]; if(sz[y] > sz[son[x]]) son[x] = y; } } void calc(int x, int ff, int rt){ if(f[X[x]]) maxn = max(maxn, f[X[x]] + dep[x] - dep[rt] * 2); if((X[x] ^ X[rt]) == 0) maxn = max(maxn, dep[x] - dep[rt]); for(int i = 0; i <= 21; ++i){ if((X[x] ^ X[rt]) == (1 << i)) maxn = max(maxn, dep[x] - dep[rt]); if(f[(X[x] ^ (1 << i))]) maxn = max(maxn, dep[x] + f[X[x] ^ (1 << i)] - dep[rt] * 2); } for(int i = hd[x]; i; i = nxt[i]){ int y = to[i]; if(y == ff || y == XQ) continue; calc(y, x, rt); } } void change(int x, int ff, int v){ if(v == 1) f[X[x]] = max(f[X[x]], dep[x]); else f[X[x]] = 0; for(int i = hd[x]; i; i = nxt[i]){ int y = to[i]; if(y == ff) continue; change(y, x, v); } } void dfs2(int x, int ff, int opt){ for(int i = hd[x]; i; i = nxt[i]){ int y = to[i]; if(y == ff || y == son[x]) continue; dfs2(y, x, 0); ans[x] = max(ans[x], ans[y]); } if(son[x]) XQ = son[x], dfs2(son[x], x, 1), ans[x] = max(ans[x], ans[son[x]]); if(f[X[x]]) maxn = max(maxn, f[X[x]] - dep[x]); for(int i = 0; i <= 21; ++i) if(f[X[x] ^ (1 << i)]) maxn = max(maxn, f[X[x] ^ (1 << i)] - dep[x]); for(int i = hd[x]; i; i = nxt[i]){ int y = to[i]; if(y == ff || y == son[x]) continue; calc(y, x, x), change(y, x, 1); } ans[x] = max(ans[x], maxn), f[X[x]] = max(f[X[x]], dep[x]), XQ = 0; if(!opt) maxn = 0, change(x, ff, -1); } bool _v; int main(){ cerr << abs(&_u - &_v) / 1048576.0 << " MB\n"; n = read(); for(int i = 2; i <= n; ++i){ int p = read(); char s; cin >> s; a[i] = 1 << (s - 'a'); add(p, i), add(i, p); } dfs1(1, 0, 0); dfs2(1, 0, 0); for(int i = 1; i <= n; ++i) printf("%d%c", ans[i], i == n ? '\n' : ' '); return 0; }
Ⅴ. P5290 [十二省联考 2019] 春节十二响
考虑有两条链的情况:将两条链弄成两个堆,每次取出两个堆的堆顶,取
然后就可以用类似的方法合并子树,复杂度
#include<bits/stdc++.h> #define ll long long using namespace std; const int N = 2e5 + 67; int read(){ int x = 0, f = 1; char ch = getchar(); while(ch < '0' || ch > '9'){if(ch == '-') f = -f; ch = getchar();} while(ch >= '0' && ch <= '9'){x = (x << 1) + (x << 3) + (ch ^ 48); ch = getchar();} return x * f; } int n; ll ans; int a[N]; vector<int> e[N], cur; priority_queue<int> pq[N]; void merge(int x, int y){ if(pq[x].size() < pq[y].size()) swap(pq[x], pq[y]); while(!pq[y].empty()){ cur.push_back(max(pq[x].top(), pq[y].top())); pq[x].pop(), pq[y].pop(); } while(!cur.empty()) pq[x].push(cur.back()), cur.pop_back(); } void dfs(int x){ for(auto y : e[x]) dfs(y), merge(x, y); pq[x].push(a[x]); } int main(){ n = read(); for(int i = 1; i <= n; ++i) a[i] = read(); for(int i = 2, f; i <= n; ++i) f = read(), e[f].push_back(i); dfs(1); while(!pq[1].empty()) ans += pq[1].top(), pq[1].pop(); printf("%lld\n", ans); return 0; }
本文作者:南风未起
本文链接:https://www.cnblogs.com/jiangchen4122/p/17455915.html
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步