树上启发式合并(DSU on tree)
使用场景
统计一棵树所有子树内结点的信息。
算法原理
贪心
特征
通常需要一个全局的桶。
实现方法
对于每个结点,总是先统计“轻”子树,并将桶清空,最后统计"重"子树,并将桶保留。
实现步骤
- 统计子树大小,将每个结点最大的子树对应的子结点设为“重儿子”
son[x]; - DFS 遍历整棵树,对于结点 x ,先统计轻子树的信息,再统计重儿子对应子树的信息,最后统计 x 为根的子树信息,视情况清空桶;
- 通常需要预处理将询问离线;
性质
- 性质1:一个点总是有 111 个重儿子,除了叶子结点;
- 性质2:一个点可能是父节点的重儿子,但不一定在重链上;
- 性质3:一个点至多往上走 logNlogNlogN 步就被合并到“重链”;
例题
CF246E
思路
- 用
set做桶,每一个深度开一个set统计字符串的不同种类数; - 离线,将 mmm 次询问挂在每个点上;
//c++set的使用
set<string> s;//定义一个元素类型为string的set
s.insert("LiveDream");//将"LiveDream"插入集合
s.size();//询问集合的元素数量
//s.erase()
template <class _Iter = iterator, enable_if_t<!is_same_v<_Iter, const_iterator>, int> = 0>
iterator erase(iterator _Where) noexcept /* strengthened */ {
const auto _Scary = _Get_scary();
#if _ITERATOR_DEBUG_LEVEL == 2
_STL_VERIFY(_Where._Getcont() == _Scary, "map/set erase iterator from incorrect container");
_STL_VERIFY(!_Where._Ptr->_Isnil, "cannot erase map/set end() iterator");
#endif // _ITERATOR_DEBUG_LEVEL == 2
return iterator(_Erase_unchecked(_Where._Unwrapped()), _Scary);
}
iterator erase(const_iterator _Where) noexcept /* strengthened */ {
const auto _Scary = _Get_scary();
#if _ITERATOR_DEBUG_LEVEL == 2
_STL_VERIFY(_Where._Getcont() == _Scary, "map/set erase iterator from incorrect container");
_STL_VERIFY(!_Where._Ptr->_Isnil, "cannot erase map/set end() iterator");
#endif // _ITERATOR_DEBUG_LEVEL == 2
return iterator(_Erase_unchecked(_Where._Unwrapped()), _Scary);
}
iterator erase(const_iterator _First, const_iterator _Last) noexcept /* strengthened */ {
return iterator(_Erase_unchecked(_First._Unwrapped(), _Last._Unwrapped()), _Get_scary());
}
size_type erase(const key_type& _Keyval) noexcept(noexcept(_Eqrange(_Keyval))) /* strengthened */ {
return _Erase(_Eqrange(_Keyval));
}
代码
#include<bits/stdc++.h>
typedef int int32;
#define int long long
using namespace std;
const int N = 1e5 + 5;
int n, m, dep[N], sum[N], son[N], heavy, ans[N];
vector<pair<int, int>>nbr[N], q[N];
set<string>cnt[N];
string s[N];
bool fa[N];
//DSU on tree start
void dfs(int x, int fa)
{
dep[x] = dep[fa] + 1;
sum[x] = 1;
for (auto& [nxt, w] : nbr[x])
if (nxt != fa)
{
dfs(nxt, x);
sum[x] += sum[nxt];
if (sum[nxt] > sum[son[x]])
son[x] = nxt;
}
return;
}
void update(int x, int v)
{
if (v == 1)
cnt[dep[x]].insert(s[x]);
else
cnt[dep[x]].clear();
for (auto& [nxt, w] : nbr[x])
if (nxt != heavy)
update(nxt, v);
return;
}
void get_ans(int x)
{
for (auto& [id, k] : q[x])
{
int pos = dep[x] + k;
if (pos > n + 1)
ans[id] = 0;
else
ans[id] = cnt[pos].size();
}
return;
}
void dfs1(int x, int y)
{
for (auto& [nxt, w] : nbr[x])
if (nxt != son[x])
dfs1(nxt, 0);
if (son[x])
dfs1(son[x], 1), heavy = son[x];
update(x, 1);
heavy = 0;
get_ans(x);
if (!y)
update(x, -1);
return;
}
//DSU on tree end
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
cin >> n;
for (int i = 1; i <= n; i++)
{
int x = i, y, z = 1;
cin >> s[i] >> y;
nbr[y].push_back({ x,z });
}
cin >> m;
for (int i = 1; i <= m; i++)
{
int x, y;
cin >> x >> y;
q[x].push_back({ i,y });
}
dfs(0, 0);
dfs1(0, 0);
for (int i = 1; i <= m; i++)
cout << ans[i] << '\n';
return 0;
}
&spm=1001.2101.3001.5002&articleId=143219887&d=1&t=3&u=c0fe9a7f9fca4618b5716d3d3b1649a5)
1296

被折叠的 条评论
为什么被折叠?



