树上启发式合并(DSU on tree)

树上启发式合并(DSU on tree)

使用场景

统计一棵树所有子树内结点的信息。

算法原理

贪心

特征

通常需要一个全局的桶。

实现方法

对于每个结点,总是先统计“轻”子树,并将桶清空,最后统计"重"子树,并将桶保留。

实现步骤

  1. 统计子树大小,将每个结点最大的子树对应的子结点设为“重儿子” son[x]
  2. DFS 遍历整棵树,对于结点 x ,先统计轻子树的信息,再统计重儿子对应子树的信息,最后统计 x 为根的子树信息,视情况清空桶;
  3. 通常需要预处理将询问离线;

性质

  • 性质1:一个点总是有 111 个重儿子,除了叶子结点;
  • 性质2:一个点可能是父节点的重儿子,但不一定在重链上;
  • 性质3:一个点至多往上走 logNlogNlogN 步就被合并到“重链”;

例题

CF246E

思路
  1. set 做桶,每一个深度开一个 set 统计字符串的不同种类数;
  2. 离线,将 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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值