补题时碰见的骚操作。可以在nlogn的时间内处理一些没有修改的,对子树的查询问题。
具体方法为
1.轻重链剖分,找出重儿子。(预处理)
2.先处理轻儿子,不记录 贡献。
3.处理重儿子,记录贡献。
4.将当前节点和轻儿子的贡献和重儿子合并。
5.如果当前节点是轻儿子的话,消除影响。
算法的正确性很容易理解。重儿子不用清空了, 因为我的兄弟节点都已经处理完了,我不会影响到兄弟节点。
时间复杂度不会证明。
hdu 4358
题目大意:询问每个节点颜色出现次数为k的颜色有多少个。
解题思路:由于对子树只有查询没有修改,可以dfs序+莫队。当时。。树上启发式合并更无脑,更快。
#include<bits/stdc++.h>
using namespace std;
#define LL long long
#define sca(x) scanf("%d",&x)
#define pb(x) push_back(x)
#define rep(i,j,k) for(int i=(j);i<=(k);i++)
#define N 100005
vector<int>G[N];
int sz[N],son[N];
int cnt[N],a[N],ou[N];
int b[N];
int Son,sum;
int n,k;
void dfs1(int u,int fa)
{
sz[u]=1;
int mx=-1;
for(int i=0;i<G[u].size();i++)
{
int v=G[u][i];
if(v!=fa)
{
dfs1(v,u);
sz[u]+=sz[v];
if(sz[v]>mx)mx=sz[v],son[u]=v;
}
}
}
void add(int u,int fa,int val)
{
if(cnt[a[u]]==k)sum--;
cnt[a[u]]+=val;
if(cnt[a[u]]==k)sum++;
for(int i=0;i<G[u].size();i++)
{
int v=G[u][i];
if(v==fa||v==Son)continue;
add(v,u,val);
}
}
void dfs(int u,int fa,int f)
{
for(int i=0; i<G[u].size(); i++)
{
int v=G[u][i];
if(v==fa||v==son[u])continue;
dfs(v,u,0);
}
if(son[u])dfs(son[u],u,1),Son=son[u];
add(u,fa,1);
ou[u]=sum;
if(son[u])Son=0;
if(!f)add(u,fa,-1),sum=0;
}
int main()
{
int t;
sca(t);
for(int cas=1; cas<=t; cas++)
{
sca(n),sca(k);
memset(son,0,sizeof(son));
sum=0,Son=0;
rep(i,1,n)G[i].clear();
rep(i,1,n)sca(a[i]),b[i]=a[i];
sort(b+1,b+1+n);
int len=unique(b+1,b+1+n)-b-1;
rep(i,1,n)a[i]=lower_bound(b+1,b+1+len,a[i])-b;
rep(i,1,n-1)
{
int x,y;
sca(x),sca(y);
G[x].pb(y);
G[y].pb(x);
}
dfs1(1,-1);
dfs(1,-1,0);
int q;
sca(q);
printf("Case #%d:\n",cas);
while(q--)
{
int tmp;
sca(tmp);
printf("%d\n",ou[tmp]);
}
if(cas!=t)printf("\n");
}
return 0;
}