树上启发式合并是一种最近几年才出现的黑科技,解决的是这么一类问题:统计树上以每一个节点为根节点的子树的信息,单组询问的话这个问题就没啥意思了,直接暴力 D F S DFS DFS统计就好了,所以一般是多组询问,例如统计子树的颜色种类,或者统计子树的某个指定颜色的节点个数,下面以第二个问题为例阐述树上启发式合并算法,如果文章有任何错误或者不严谨的地方,欢迎各位批评指正
int cnt[maxn];
void add(int v, int p, int x){
cnt[ col[v] ] += x;
for(auto u: g[v])
if(u != p)
add(u, v, x)
}
void dfs(int v, int p){
add(v, p, 1);
//now cnt[c] is the number of vertices in subtree of vertex v that has color c.
//You can answer the queries easily.
add(v, p, -1);
for(auto u : g[v])
if(u != p)
dfs(u, v);
}
int cnt[maxn];
bool big[maxn];
void add(int v, int p, int x){
cnt[ col[v] ] += x;
for(auto u: g[v])
if(u != p && !big[u])
add(u, v, x)
}
void dfs(int v, int p, bool keep){
int mx = -1, bigChild = -1;
for(auto u : g[v])
if(u != p && sz[u] > mx)
mx = sz[u], bigChild = u;
for(auto u : g[v])
if(u != p && u != bigChild)
dfs(u, v, 0); // run a dfs on small childs and clear them from cnt
if(bigChild != -1)
dfs(bigChild, v, 1), big[bigChild] = 1; // bigChild marked as big and not cleared from cnt
add(v, p, 1);
//now cnt[c] is the number of vertices in subtree of vertex v that has color c. You can answer the queries easily.
if(bigChild != -1)
big[bigChild] = 0;
if(keep == 0)
add(v, p, -1);
}
#include
#include
#include
#include
#include
#include
using namespace std;
const int maxn=100005;
//题目数据
vector<int> vec[maxn];
int t,n,k,q,a[maxn],b[maxn],tot,u,v,cnt[maxn],sum,ans[maxn];
//dfs序
int tim,in[maxn],out[maxn],fin[maxn],val[maxn];
template<typename T>
inline void read(T &x) {
x = 0;int f = 1;char ch=getchar();
while(ch<'0'||ch>'9') {
if(ch=='-') f=-1;
ch=getchar();
}
while(ch>='0'&&ch<='9'){
x=x*10+ch-'0';
ch=getchar();
}x=x*f;
}
void init()
{
tim=0;
for(int i=1;i<=n;i++) vec[i].clear();
}
void dfs(int cur,int fa)
{
in[cur]=++tim;fin[tim]=cur;val[tim]=a[cur];
for(int i=0;i<vec[cur].size();i++){
if(vec[cur][i]!=fa){
dfs(vec[cur][i],cur);
}
}
out[cur]=tim;
}
struct node{
int l,r,id,block;
node(int a=0,int b=0,int c=0){
l=a;r=b;id=c;block=c/sqrt(n);
}
friend bool operator<(const node &a,const node &b){
return a.block==b.block?a.r<b.r:a.l<b.l;
}
}query[maxn];
void del(int pos)
{
if(cnt[val[pos]]==k) sum--;
cnt[val[pos]]--;
if(cnt[val[pos]]==k) sum++;
}
void add(int pos)
{
if(cnt[val[pos]]==k) sum--;
cnt[val[pos]]++;
if(cnt[val[pos]]==k) sum++;
}
void modui()
{
sum=0;int l=1,r=0;
for(int i=1;i<=tot;i++) cnt[i]=0;
for(int i=1;i<=q;i++){
while(l<query[i].l) del(l++);
while(l>query[i].l) add(--l);
while(r<query[i].r) add(++r);
while(r>query[i].r) del(r--);
ans[query[i].id]=sum;
}
}
int main()
{
read(t);
for(int cas=1;cas<=t;cas++){
read(n);read(k);
init();
for(int i=1;i<=n;i++) read(a[i]),b[i]=a[i];
sort(b+1,b+n+1);
tot=unique(b+1,b+n+1)-b-1;
for(int i=1;i<=n;i++) a[i]=lower_bound(b+1,b+tot+1,a[i])-b;
for(int i=1;i<n;i++){
read(u);read(v);
vec[u].push_back(v);
vec[v].push_back(u);
}
dfs(1,0);
read(q);
for(int i=1;i<=q;i++) {
read(u);
query[i]=node(in[u],out[u],i);
}
sort(query+1,query+q+1);
modui();
printf("Case #%d:\n",cas);
for(int i=1;i<=q;i++) printf("%d\n",ans[i]);
if(cas<t) printf("\n");
}
}
#include
#include
#include
#include
using namespace std;
const int maxn=100005;
//题目数据
int t,n,u,v,q,k,a[maxn],b[maxn],ans[maxn],cnt[maxn],sum,m;
vector<int> vec[maxn];
//树剖用
int tot=0,siz[maxn],son[maxn];
int vis[maxn];
void dfs1(int cur,int fath,int he){ //dfs(root,0,1)
siz[cur]=1;
for(int i=0;i<vec[cur].size();i++){
if(vec[cur][i]!=fath){
dfs1(vec[cur][i],cur,he+1);
siz[cur]+=siz[vec[cur][i]];
if(siz[vec[cur][i]]>siz[son[cur]]) son[cur]=vec[cur][i];
}
}
}
void calc(int cur,int fa,int val)
{
if(cnt[a[cur]]==k) sum--;
cnt[a[cur]]+=val;
if(cnt[a[cur]]==k) sum++;
for(int i=0;i<vec[cur].size();i++){
if(vec[cur][i]!=fa&&!vis[vec[cur][i]]){ //!vis[vec[cur][i]]表示不计算重儿子的贡献,因为已经计算过
calc(vec[cur][i],cur,val);
}
}
}
void dfs(int cur,int fa,bool keep) //keep表示以当前节点为根节点的子树的贡献是否保留
{
for(int i=0;i<vec[cur].size();i++){
if(vec[cur][i]!=fa&&vec[cur][i]!=son[cur]){
dfs(vec[cur][i],cur,0);//计算轻链的结果,并且不保存
}
if(son[cur]) dfs(son[cur],cur,1),vis[son[cur]]=1; //计算重儿子贡献,并打上标记,防止下一次calc的时候重复计算
calc(cur,fa,1);//计算当前节点和所有以轻儿子为根节点的子树的贡献
ans[cur]=sum; //这里就计算好了整颗以当前节点为根节点的子树的贡献
if(son[cur]) vis[son[cur]]=0; //消除标记,防止对下一步的清空操作产生影响,因为如果当前节点是轻儿子的话,那么整颗以
//当前节点为根节点的子树的贡献都要清空,如果不把重儿子的标记去掉,那么以重儿子为根的
//子树的贡献就无法在下一步清空
if(!keep) calc(cur,fa,-1); //如果当前节点作为父节点的轻儿子,那么消除以当前节点为根节点的子树的影响
}
void init()
{
tot=0;sum=0;
memset(son,0,sizeof(son));
memset(cnt,0,sizeof(cnt));
memset(vis,0,sizeof(vis));
for(int i=1;i<=n;i++) vec[i].clear();
}
int main()
{
scanf("%d",&t);
for(int cas=1;cas<=t;cas++){
scanf("%d %d",&n,&k);init();
for(int i=1;i<=n;i++) scanf("%d",&a[i]),b[i]=a[i];
sort(b+1,b+n+1);
m=unique(b+1,b+n+1)-b-1;
for(int i=1;i<=n;i++) a[i]=lower_bound(b+1,b+m+1,a[i])-b;
for(int i=1;i<n;i++){
scanf("%d %d",&u,&v);
vec[u].push_back(v);
vec[v].push_back(u);
}
dfs1(1,0,1);
dfs(1,0,0);
scanf("%d",&q);
printf("Case #%d:\n",cas);
for(int i=1;i<=q;i++){
scanf("%d",&u);
printf("%d\n",ans[u]);
}
if(cas<t) printf("\n");
}
}
You are given a rooted tree with root in vertex 1 1 1. Each vertex is coloured in some colour.
Let’s call colour c c c dominating in the subtree of vertex v v v if there are no other colours that appear in the subtree of vertex v v v more times than colour c c c. So it’s possible that two or more colours will be dominating in the subtree of some vertex.
The subtree of vertex v v v is the vertex v v v and all other vertices that contains vertex v v v in each path to the root.
For each vertex v v v find the sum of all dominating colours in the subtree of vertex v v v.
The first line contains integer n ( 1 ≤ n ≤ 1 0 5 ) n (1 ≤ n ≤ 10^5) n(1 ≤ n ≤ 105) — the number of vertices in the tree.
The second line contains n integers c i ( 1 ≤ c i ≤ n ) , c i c_i (1 ≤ c_i ≤ n), c_i ci(1 ≤ ci ≤ n),ci — the colour of the i i i-th vertex.
Each of the next n − 1 n - 1 n − 1 lines contains two integers x j , y j ( 1 ≤ x j , y j ≤ n ) x_j, y_j (1 ≤ x_j, y_j ≤ n) xj, yj(1 ≤ xj, yj ≤ n) — the edge of the tree. The first vertex is the root of the tree.
Print n n n integers — the sums of dominating colours for each vertex.
4
1 2 3 4
1 2
2 3
2 4
10 9 3 4
15
1 2 3 1 2 3 3 1 1 3 2 2 1 2 3
1 2
1 3
1 4
1 14
1 15
2 5
2 6
2 7
3 8
3 9
3 10
4 11
4 12
4 13
6 5 4 3 2 3 3 1 1 3 2 2 1 2 3
#include
using namespace std;
const int maxn=1e5+10;
//题目数据
int col[maxn],n,u,v,vis[maxn],cnt[maxn],maxx=0;//sum表示颜色个数为i的节点颜色之和,cnt表示颜色为i的节点数量
vector<int> vec[maxn];
long long ans[maxn],sum[maxn];
//树剖用
int tot=0,siz[maxn],son[maxn];
void dfs1(int cur,int fa)
{
siz[cur]=1;
for(int i=0;i<vec[cur].size();i++){
if(vec[cur][i]!=fa){
dfs1(vec[cur][i],cur);
siz[cur]+=siz[vec[cur][i]];
if(siz[vec[cur][i]]>siz[son[cur]]) son[cur]=vec[cur][i];
}
}
}
void calc(int cur,int fa,int val)
{
sum[cnt[col[cur]]]-=col[cur];
cnt[col[cur]]+=val;
sum[cnt[col[cur]]]+=col[cur];
if(cnt[col[cur]]>maxx){
maxx=cnt[col[cur]];
}
if(sum[maxx]==0) maxx=cnt[col[cur]];
for(int i=0;i<vec[cur].size();i++){
if(vec[cur][i]!=fa&&!vis[vec[cur][i]]){
calc(vec[cur][i],cur,val);
}
}
}
void dfs(int cur,int fa,int keep)
{
for(int i=0;i<vec[cur].size();i++){
if(vec[cur][i]!=fa&&vec[cur][i]!=son[cur]){
dfs(vec[cur][i],cur,0);
}
}
if(son[cur]) dfs(son[cur],cur,1),vis[son[cur]]=1;
calc(cur,fa,1);
ans[cur]=sum[maxx];
if(son[cur]) vis[son[cur]]=0;
if(!keep) calc(cur,fa,-1);
}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++) scanf("%d",&col[i]);
for(int i=1;i<n;i++){
scanf("%d %d",&u,&v);
vec[u].push_back(v);
vec[v].push_back(u);
}
dfs1(1,0);
dfs(1,0,1);
for(int i=1;i<=n;i++) printf("%lld%c",ans[i],i==n?'\n':' ');
}