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  阅读(26)  评论(0编辑  收藏  举报
  1. 1 イエスタデイ(翻自 Official髭男dism) 茶泡饭,春茶,kobasolo
  2. 2 光辉岁月 Audio artist
  3. 3 名前を呼ぶよ Audio artist
  4. 4 战歌 Audio artist
  5. 5 時を越えた想い Audio artist
  6. 6 所念皆星河 Audio artist
  7. 7 See you again Audio artist
イエスタデイ(翻自 Official髭男dism) - 茶泡饭,春茶,kobasolo
00:00 / 00:00
An audio error has occurred, player will skip forward in 2 seconds.

作词 : 藤原聡

作曲 : 藤原聡

何度失ったって

取り返して見せるよ

雨上がり 虹がかかった空みたいな

君の笑みを

例えばその代償に

誰かの表情を

曇らせてしまったっていい

悪者は僕だけでいい

本当はいつでも

誰もと思いやりあっていたい

でもそんな悠長な理想論は

ここで捨てなくちゃな

遥か先で 君へ 狙いを定めた恐怖を

遥か先で 君へ 狙いを定めた恐怖を

どれだけ僕は

はらい切れるんだろう?

半信半疑で 世間体

半信半疑で 世間体

気にしてばっかのイエスタデイ

ポケットの中で怯えたこの手は

まだ忘れられないまま

「何度傷ついたって

「何度傷ついたって

仕方ないよ」と言って

うつむいて君が溢した

儚くなまぬるい涙

ただの一粒だって

僕を不甲斐なさで 溺れさせて

理性を奪うには十分過ぎた

街のクラクションもサイレンも

街のクラクションもサイレンも

届きやしないほど

遥か先へ進め 身勝手すぎる恋だと

遥か先へ進め 身勝手すぎる恋だと

世界が後ろから指差しても

振り向かず進め必死で

振り向かず進め必死で

君の元へ急ぐよ

道の途中で聞こえたSOS さえ

気づかないふりで

バイバイイエスタデイ ごめんね

バイバイイエスタデイ ごめんね

名残惜しいけど行くよ

いつかの憧れと違う僕でも

ただ1人だけ 君だけ

守るための強さを

何よりも望んでいた この手に今

遥か先へ進め

遥か先へ進め

幼すぎる恋だと

世界が後ろから指差しても

迷わずに進め 進め

2人だけの宇宙へと

ポケットの中で震えたこの手で今

君を連れ出して

未来の僕は知らない

だから視線は止まらない

謎めいた表現技法

意味深な君の気性

アイラブユーさえ

アイラブユーさえ

風に 飛ばされそうな時でも

不器用ながら繋いだ この手はもう

決して離さずに

虹の先へ