这是个用来处理这样一类题目的:询问支持离线,并且询问与子树有关。
它可以很方便地在 O ( n l o g n ) O(nlogn) O(nlogn) 的时间内完成。
它是一个利用了重链剖分的一个性质的暴力。
先掏一道模板题来讲吧:
给出一棵节点有颜色的树,每次询问某个节点的子树内有多少种不同的颜色。
我们考虑离线做这个问题,先求出每个节点的答案,然后每次直接 O ( 1 ) O(1) O(1) 回答即可。
先考虑暴力,显然可以递归遍历一次整棵树,对于遍历到的每个节点,我暴力再统计一次它的子树信息然后求解,然后删去它的子树信息。我们思考一下,这个暴力劣在哪里?可以发现,对于一个节点 x x x 的最后一个遍历到的儿子,我们不需要删去它的信息,它的信息可以直接给父亲用,然后父亲就只需要统计其他的儿子的子树内的信息就够了。
那么根据贪心的思想,我们肯定要让最后一个遍历到的儿子的子树尽可能大,那么显然这个儿子就是 x x x 的重儿子了。
然后就做完了。(没想到吧,就这么简单)
读者:滚蛋,这不还是个暴力吗,怎么证明时间复杂度?
好吧,接下来证明一下这样做暴力的时间复杂度是 O ( n l o g n ) O(nlogn) O(nlogn) 的。
我们考虑每个节点的信息会被统计的次数:在这个节点到根节点的路径上,每有一条轻边,这个节点就会被统计一次。
因为重链剖分可以保证,任意一个节点到根节点的路径上轻边的数量不超过 log n \log n logn条,所以总的时间复杂度是 O ( n l o g n ) O(nlogn) O(nlogn) 的。
代码:
#include
#include
#define maxn 100010
int n,m,col[maxn];
struct edge{int y,next;};
edge e[maxn*2];
int first[maxn];
void buildroad(int x,int y)
{
static int len=0;
e[++len]=(edge){y,first[x]};
first[x]=len;
}
int size[maxn],mson[maxn];
void dfs1(int x,int fa)//求重儿子
{
size[x]=1;
for(int i=first[x];i;i=e[i].next)
{
int y=e[i].y;
if(y==fa)continue;
dfs1(y,x);
if(size[y]>size[mson[x]])mson[x]=y;
size[x]+=size[y];
}
}
int tong[maxn],ans[maxn],now_ans=0;
void go(int x,int fa,int type)
{
tong[col[x]]+=type;
if(type==1&&tong[col[x]]==1)now_ans++;
if(type==-1&&tong[col[x]]==0)now_ans--;
for(int i=first[x];i;i=e[i].next)
if(e[i].y!=fa)go(e[i].y,x,type);
}
void dfs2(int x,int fa,bool del)
//求解,del表示求完x的子树的答案后需不需要清空x的子树的信息
{
for(int i=first[x];i;i=e[i].next)//先统计轻儿子的答案
if(e[i].y!=fa&&e[i].y!=mson[x])dfs2(e[i].y,x,true);
if(mson[x]!=0)dfs2(mson[x],x,false);//最后统计重儿子的答案
tong[col[x]]++;if(tong[col[x]]==1)now_ans++;//统计自己以及轻子树的信息
for(int i=first[x];i;i=e[i].next)
if(e[i].y!=fa&&e[i].y!=mson[x])go(e[i].y,x,1);
ans[x]=now_ans;//得到自己的答案
if(del)go(x,fa,-1);//假如要删掉自己的信息,就暴力地删掉
}
int main()
{
scanf("%d",&n);
for(int i=1,x,y;i<n;i++)
scanf("%d %d",&x,&y),buildroad(x,y),buildroad(y,x);
for(int i=1;i<=n;i++)
scanf("%d",&col[i]);
dfs1(1,0);
dfs2(1,0,false);
scanf("%d",&m);
for(int i=1,x;i<=m;i++)
scanf("%d",&x),printf("%d\n",ans[x]);
}
题目传送门
还是一颗节点有颜色的树,需要统计每个棵子树内出现次数最多的颜色之和。
板子题,直接上代码:
#include
#include
#define maxn 100010
int n,col[maxn];
struct edge{int y,next;};
edge e[maxn*2];
int first[maxn];
void buildroad(int x,int y)
{
static int len=0;
e[++len]=(edge){y,first[x]};
first[x]=len;
}
int size[maxn],mson[maxn];
void dfs_getmson(int x,int fa)
{
size[x]=1;
for(int i=first[x];i;i=e[i].next)
{
int y=e[i].y;
if(y==fa)continue;
dfs_getmson(y,x);
if(size[y]>size[mson[x]])mson[x]=y;
size[x]+=size[y];
}
}
long long ans[maxn],now_ans=0;
int tong[maxn],max_col=0;
void go(int x,int fa,int type)
{
tong[col[x]]+=type;
if(type==1)
{
if(tong[col[x]]>max_col)max_col=tong[col[x]],now_ans=col[x];
else if(tong[col[x]]==max_col)now_ans+=col[x];
}
for(int i=first[x];i;i=e[i].next)
if(e[i].y!=fa)go(e[i].y,x,type);
}
void dfs_getans(int x,int fa,bool del)
{
for(int i=first[x];i;i=e[i].next)
if(e[i].y!=fa&&e[i].y!=mson[x])dfs_getans(e[i].y,x,true);
if(mson[x]!=0)dfs_getans(mson[x],x,false);
for(int i=first[x];i;i=e[i].next)
if(e[i].y!=fa&&e[i].y!=mson[x])go(e[i].y,x,1);
tong[col[x]]++;
if(tong[col[x]]>max_col)max_col=tong[col[x]],now_ans=col[x];
else if(tong[col[x]]==max_col)now_ans+=col[x];
ans[x]=now_ans;if(del)go(x,fa,-1),max_col=0,now_ans=0;
}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
scanf("%d",&col[i]);
for(int i=1,x,y;i<n;i++)
scanf("%d %d",&x,&y),buildroad(x,y),buildroad(y,x);
dfs_getmson(1,0);
dfs_getans(1,0,false);
for(int i=1;i<=n;i++)
printf("%lld ",ans[i]);
}