DSU

树上启发式合并

适用于维护子树内信息

例题:CF246E

思路

  1. 暴力
    将询问离线下来,挂在每个点上
    对于每个点 \(x\),维护一个桶 \(cnt_{dep}\),统计深度 \(dep\) 下不同字符串出现的次数
    对于 \(x\) 上的询问输出 \(cnt_{dep_x+k}\)
    每切换一个 \(x\)\(cnt\) 要重置

  2. \(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;
}
posted @ 2024-04-09 13:10  yabnto  阅读(15)  评论(0编辑  收藏  举报