前言
只是一个神奇的暴力。
来源
为了快速统计与合并子树信息,最简单的就是每个节点分配一个空间用来存状态。这样做空间开不下,暴力合并信息也一定是高复杂度。所以我们只能给全局开一个空间让他来存状态。每次获取子树信息再重新dfs子树就行了。这样空间优了,复杂度也优了。
做法
每次把儿子的信息给自己,如果全局只有一个空间来存状态(比如:桶),那么显然,转移是0,因为与儿子共用一个空间存状态。但这样的话,你的兄弟也可以用到你的信息,所以你的兄弟在进行dfs时需要把桶清空。总结来看,真正到节点i时,桶内也只有他一个儿子的信息。这么来看,那一个儿子的选取十分重要。
显然选重儿子,如果只能选一个的话。换句话说,每次就还要重新遍历所有轻儿子。
复杂度O(nlogn),由重儿子性质保证
具体做法
每一层都需要先遍历轻儿子。然后,再遍历重儿子,再去遍历这一棵树,这次不能遍历重儿子为根的子树。
大概就是三个dfs。
例题
Lomsat gelral
一棵有根树,以1为根,一种颜色占领了某棵子树当且仅当该颜色在该棵树内数量最多。输出每棵子树被占领颜色的和。
其实是个板子题。贴一份代码
#include<bits/stdc++.h>
#define LL long long
using namespace std;
inline char gc(){
static char buf[1<<7],*p1=buf,*p2=buf;
return (p1==p2)&&(p2=(p1=buf)+fread(buf,1,1<<7,stdin),p1==p2)?EOF:*p1++;
}
template <class T>
inline void read(T&data){
data=0;
register char ch=0;
while(ch<'0'||ch>'9')ch=getchar();
while(ch<='9'&&ch>='0'){
data=(data<<3)+(data<<1)+(ch^48);
ch=getchar();
}
return;
}
const int _ = 1e5+5;
int son[_],head[_];
bool vis[_];
struct edge{
int to,nt;
}e[_<<1];
int cnt,n;
int tong[_];
int size[_],c[_];
LL ans[_],mx,sum;
void add(register int a,register int b){
e[++cnt].to=a,e[cnt].nt=head[b],head[b]=cnt;
}
void dfs(register int now,register int fa){
size[now]=1;
register int mmx=0;
for(register int i=head[now];i;i=e[i].nt){
if(e[i].to==fa)continue;
dfs(e[i].to,now);
size[now]+=size[e[i].to];
if(size[e[i].to]>mmx)mmx=size[e[i].to],son[now]=e[i].to;
}
return;
}
void dfs3(register int now,register int fa,register int pd){
if(vis[now]==0)tong[c[now]]+=pd;//如果不是重儿子,则添加到桶中或者删
if(pd>0&&tong[c[now]]==mx)sum+=c[now];//如果是来找信息的
if(pd>0&&tong[c[now]]>mx){
mx=tong[c[now]];sum=c[now];
}
for(register int i=head[now];i;i=e[i].nt){
if(e[i].to==fa||vis[e[i].to])continue;
dfs3(e[i].to,now,pd);
}
}
void dfs2(register int now,register int fa,register int pd){
for(register int i=head[now];i;i=e[i].nt){
if(e[i].to!=fa&&e[i].to!=son[now]){
dfs2(e[i].to,now,0);
}
}
if(son[now]){
dfs2(son[now],now,1);vis[son[now]]=1;
}
dfs3(now,fa,1);ans[now]=sum;
if(son[now]!=0) vis[son[now]]=0;
if(pd==0)dfs3(now,fa,-1),mx=sum=0;
}
int main(){
read(n);
for(register int i=1;i<=n;++i)read(c[i]);
for(register int i=1;i<n;++i){
register int a,b;
read(a);read(b);
add(a,b),add(b,a);
}
dfs(1,0);
dfs2(1,0,0);
for(register int i=1;i<=n;++i){
cout<<ans[i]<<' ';
}
}
Tree Requests
大意就是每个节点有一个字符。问某一棵子树内深度为d的节点的字符能否构成回文串。
又是板子,把字符变成二进上的每一位,然后搞个桶,如果等于0或这只有一位为1,就是可以的。
代码
#include<bits/stdc++.h>
using namespace std;
const int _ = 5e5+2e2;
int tong[_],head[_];
struct edge{
int nt,to;
}e[_];
int col[_],cnt,vnt,son[_],deep[_],fir[_],size[_],n,m;
char s[_];
bool vis[_];
struct zjy{
int nt,to,ans;//to是指深度
}A[_];
inline void add(register int a,register int b){
e[++cnt].to=b,e[cnt].nt=head[a],head[a]=cnt;
}
void dfsI(register int now){
size[now]=1;
for(register int i=head[now];i;i=e[i].nt){
deep[e[i].to]=deep[now]+1;
dfsI(e[i].to);
size[now]+=size[e[i].to];
if(size[son[now]]<size[e[i].to])son[now]=e[i].to;
}
return;
}
void dfsIII(register int now,register int pd){
if(vis[now]==0)tong[deep[now]]^=(1<<col[now]);
for(register int i=head[now];i;i=e[i].nt){
if(vis[e[i].to]==1)continue;
dfsIII(e[i].to,pd);
}
}
inline int lowbit(register int k){
return k&(-k);
}
void dfsII(register int now,register int pd){//听说int比bool还快???????
for(register int i=head[now];i;i=e[i].nt){
if(e[i].to==son[now])continue;
dfsII(e[i].to,0);
}
if(son[now]){
dfsII(son[now],1);
vis[son[now]]=1;
}
dfsIII(now,1);
for(register int i=fir[now];i;i=A[i].nt){
if(tong[A[i].to]==0){A[i].ans=1;continue;}
if((tong[A[i].to]^(lowbit(tong[A[i].to]) ) )==0){A[i].ans=1;continue;}
}
if(son[now])vis[son[now]]=0;
if(pd==0)dfsIII(now,0);
}
inline void Add(register int a,register int b){
A[++vnt].to=b,A[vnt].nt=fir[a],fir[a]=vnt;
}
int main(){
scanf("%d%d",&n,&m);
for(register int i=2,a;i<=n;++i){
scanf("%d",&a);
add(a,i);
}
scanf("%s",s+1);
for(register int i=1;i<=n;++i){
col[i]=s[i]-'a';//表示右移多少位
}
deep[1]=1;
dfsI(1);
for(register int i=1,a,b;i<=m;++i){
scanf("%d%d",&a,&b);
Add(a,b);
}
dfsII(1,1);//这个1就是用来判断其是否属于其父亲的重儿子的
for(register int i=1;i<=m;++i){
if(A[i].ans)puts("Yes");
else puts("No");
}
}