Dsu on tree 代指树上启发式合并,并非是并查集个人觉得这个算法的思想跟莫队有些许相似,但是又利用了树链剖分的一些性质,从而使得复杂度大大降低,优秀的o(nlgn)。
需要的前置技能:链式前向星,树链剖分。
U41492 树上数颜色
给出一棵结点有不同颜色的数,询问某个子树有多少种不同的颜色?
传送门
思考一下最暴力的做法,对于每个结点都开一个cnt数组,记录自己的颜色,然后往上合并,处理完以后提供查询,显然空间得炸!
我们可以很显然的看到,一个节点是由它的子节点的合并而来,那么如果我们使用一个数组,去记录某个儿子,然后记录到答案以后再清空,再给下一个儿子使用,这样可以吗?显然是可以的,但是时间复杂度就不允许了?怎么办?
树链剖分的性质:一个点到根路径上不超过log条轻边,等价于一个点最多会被暴力合并logn次
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
int cnt, head[N];
struct E
{
int to, nex;
} e[N << 1];
inline void add_edge(int u, int v)
{
e[++cnt].to = v, e[cnt].nex = head[u], head[u] = cnt;
}
int siz[N], son[N];
void dfs(int u, int fa)
{
siz[u] = 1;
for(int i = head[u]; i; i = e[i].nex)
{
int v = e[i].to;
if(v == fa) continue;
dfs(v, u);
siz[u] += siz[v];
if(siz[v] > siz[son[u]]) son[u] = v;
}
}
int a[N], ok[N], cot[N], sum;
void cal(int u, int fa, int val)
{
cot[a[u]] += val;
if(val == 1 && cot[a[u]] == 1) sum++;
if(val == -1 && cot[a[u]] == 0) sum--;
for(int i = head[u]; i; i = e[i].nex)
{
int v = e[i].to;
if(v != fa) cal(v, u, val);
}
}
void dsu(int u, int fa, bool flag)
{
for(int i = head[u]; i; i = e[i].nex)
{
int v = e[i].to;
if(v != fa && v != son[u]) dsu(v, u, true);
}
if(son[u] != 0) dsu(son[u], u, false);
cot[a[u]]++;
if(cot[a[u]] == 1) sum++;
for(int i = head[u]; i; i = e[i].nex)
{
int v = e[i].to;
if(v != fa && v != son[u]) cal(v, u, 1);
}
ok[u] = sum;
if(flag) cal(u, fa, -1);
}
int n, m;
signed main()
{
ios::sync_with_stdio(false), cin.tie(0);
cin >> n;
for(int i = 1, u, v; i < n; ++i)
{
cin >> u >> v;
add_edge(u, v), add_edge(v, u);
}
for(int i = 1; i <= n; ++i) cin >> a[i];
dfs(1, 0);
dsu(1, 0, 0);
cin >> m;
int x;
while(m-- && cin >> x) cout << ok[x] << "\n";
return 0;
}
标签:cot,int,sum,Dsu,tree,son,fa,&&
From: https://www.cnblogs.com/-ytz/p/16597513.html