DSU
树上启发式合并
适用于维护子树内信息
例题:CF246E
思路
-
暴力
将询问离线下来,挂在每个点上
对于每个点 \(x\),维护一个桶 \(cnt_{dep}\),统计深度 \(dep\) 下不同字符串出现的次数
对于 \(x\) 上的询问输出 \(cnt_{dep_x+k}\)
每切换一个 \(x\),\(cnt\) 要重置 -
\(DSU\) 优化
维护每个子树的大小 \(size_x\),以及每个点 \(x\) 最大的字数大小的节点 \(son_x\)
定义 \(x\) 下最大的字数大小为 \(x\) 的重儿子,其余为轻儿子
设从根往下走,每次只走重儿子的链叫重链
对于离线下来的询问,约定根节点为 \(x\),先遍历 \(x\) 的轻儿子,在遍历重儿子
此时对于重儿子的 \(cnt\) 将不会被重构
那么重链上的点只被遍历一遍,只有轻儿子会被重复遍历,接下来就变成了并查集的按秩合并
code
#include <algorithm>
#include <iostream>
#include <set>
#include <vector>
using namespace std;
const int MaxN = 1e5 + 10;
int fa[MaxN], sz[MaxN], d[MaxN], son[MaxN], ans[MaxN], n, m, heavy, tot;
vector<int> g[MaxN];
vector<pair<int, int>> q[MaxN];
set<string> cnt[MaxN];
string s[MaxN];
void FB(int x) {
sz[x] = 1;
for (int i : g[x]) {
d[i] = d[x] + 1;
FB(i);
sz[x] += sz[i];
if (sz[son[x]] < sz[i]) son[x] = i;
}
}
void update(int x, int v) {
if (v == 1) {
cnt[d[x]].insert(s[x]);
} else {
cnt[d[x]].clear();
}
for (int i : g[x]) {
if (i == heavy) continue;
update(i, v);
}
}
void G(int x) {
for (auto i : q[x]) {
int id = i.second;
int k = i.first;
int pos = d[x] + k;
if (pos <= n) ans[id] = cnt[pos].size();
}
}
void DFS(int x, int flag) {
for (int i : g[x]) {
if (i == son[x]) continue;
DFS(i, 0);
}
if (son[x]) DFS(son[x], 1), heavy = son[x];
update(x, 1);
G(x);
heavy = 0;
if (!flag) update(x, -1);
}
int main() {
cin >> n;
for (int i = 1, x; i <= n; i++) {
cin >> s[i] >> x;
g[x].push_back(i);
}
cin >> m;
for (int i = 1, u, k; i <= m; i++) {
cin >> u >> k;
q[u].push_back({k, i});
}
FB(0);
DFS(0, 1);
for (int i = 1; i <= m; i++) {
cout << ans[i] << '\n';
}
return 0;
}
CF570D Tree Requests
思路
其实和例题一样,考虑什么样的串是不能重排形成回文的,很显然,如果出现了两个字母的个数除 \(2\) 余 \(1\) 的字母,那么就必然会有一个不能再中心,从而使回文串不回文,所以统计每个字母的数量,然后就和例题一样了。
code
#include <algorithm>
#include <iostream>
#include <set>
#include <vector>
using namespace std;
const int MaxN = 500010;
int cnt[MaxN][27], fa[MaxN], sz[MaxN], d[MaxN], son[MaxN], ans[MaxN], sum[MaxN], n, m, heavy;
vector<int> g[MaxN];
vector<pair<int, int>> q[MaxN];
string s;
void FB(int x) {
sz[x] = 1;
for (int i : g[x]) {
d[i] = d[x] + 1;
FB(i);
sz[x] += sz[i];
if (sz[son[x]] < sz[i]) son[x] = i;
}
}
void update(int x, int v) {
if (v == 1) {
if (cnt[d[x]][s[x - 1] - 'a'] == 0 || (cnt[d[x]][s[x - 1] - 'a'] > 1 && cnt[d[x]][s[x - 1] - 'a'] % 2 == 0)) {
sum[d[x]]++;
}
cnt[d[x]][s[x - 1] - 'a']++;
if ((cnt[d[x]][s[x - 1] - 'a'] > 1 && cnt[d[x]][s[x - 1] - 'a'] % 2 == 0)) {
sum[d[x]]--;
}
} else {
if ((cnt[d[x]][s[x - 1] - 'a'] > 1 && cnt[d[x]][s[x - 1] - 'a'] % 2 == 0)) {
sum[d[x]]++;
}
cnt[d[x]][s[x - 1] - 'a']--;
if (cnt[d[x]][s[x - 1] - 'a'] == 0 || (cnt[d[x]][s[x - 1] - 'a'] > 1 && cnt[d[x]][s[x - 1] - 'a'] % 2 == 0)) {
sum[d[x]]--;
}
}
for (int i : g[x]) {
if (i == heavy) continue;
update(i, v);
}
}
void G(int x) {
for (auto i : q[x]) {
int id = i.second;
int k = i.first;
ans[id] += (sum[k] <= 1);
}
}
void DFS(int x, int flag) {
for (int i : g[x]) {
if (i == son[x]) continue;
DFS(i, 0);
}
if (son[x]) DFS(son[x], 1), heavy = son[x];
update(x, 1);
G(x);
heavy = 0;
if (!flag) update(x, -1);
}
int main() {
ios::sync_with_stdio(0), cin.tie(0);
cin >> n >> m;
for (int i = 2, fa; i <= n; i++) {
cin >> fa;
g[fa].push_back(i);
}
cin >> s;
for (int i = 1, u, k; i <= m; i++) {
cin >> u >> k;
q[u].push_back({k, i});
}
d[1] = 1, FB(1);
DFS(1, 1);
for (int i = 1; i <= m; i++) {
cout << (ans[i] ? "Yes" : "No") << '\n';
}
return 0;
}
CF600E Lomsat gelral
思路
由于是子树,所以我们可以将深度的维度去掉,然后在修改时维护最大值即可。
code
#include <algorithm>
#include <iostream>
#include <unordered_map>
#include <vector>
using namespace std;
const int MaxN = 1e5 + 10, start = 0, endd = MaxN - 1;
int cnt[MaxN], fa[MaxN], sz[MaxN], son[MaxN], c[MaxN], nxt[MaxN], pre[MaxN], n, m, heavy;
long long ans[MaxN], maxx, sum;
vector<int> g[MaxN];
bool vis[MaxN];
void FB(int x, int fa) {
sz[x] = 1;
for (int i : g[x]) {
if (i == fa) continue;
FB(i, x);
sz[x] += sz[i];
if (sz[son[x]] < sz[i]) son[x] = i;
}
}
void update(int x, int v, int fa) {
for (int i : g[x]) {
if (i == heavy || i == fa) continue;
update(i, v, x);
}
cnt[c[x]] += v;
if (v == 1) {
if (maxx == cnt[c[x]]) {
sum += c[x];
}
if (maxx < cnt[c[x]]) {
maxx = cnt[c[x]];
sum = c[x];
}
}
if (!vis[c[x]] && cnt[c[x]] > 0) {
pre[c[x]] = pre[endd], nxt[pre[endd]] = c[x], nxt[c[x]] = endd, pre[endd] = c[x], vis[c[x]] = 1;
}
if (cnt[c[x]] == 0 && vis[c[x]]) {
vis[c[x]] = 0;
nxt[pre[c[x]]] = nxt[c[x]], pre[nxt[c[x]]] = pre[c[x]];
}
}
void G(int x) {
ans[x] += sum;
}
void DFS(int x, int flag, int fa) {
for (int i : g[x]) {
if (i == son[x] || i == fa) continue;
DFS(i, 0, x);
}
if (son[x]) DFS(son[x], 1, x), heavy = son[x];
update(x, 1, fa);
G(x);
heavy = 0;
if (!flag) update(x, -1, fa), maxx = sum = 0;
}
int main() {
ios::sync_with_stdio(0), cin.tie(0);
nxt[start] = endd, pre[endd] = start;
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> c[i];
}
for (int i = 1, u, v; i < n; i++) {
cin >> u >> v;
g[u].push_back(v);
g[v].push_back(u);
}
FB(1, 0);
DFS(1, 0, 0);
for (int i = 1; i <= n; i++) {
cout << ans[i] << ' ';
}
return 0;
}