树上暴力 之 树上启发式合并

前言

只是一个神奇的暴力。

来源

为了快速统计与合并子树信息,最简单的就是每个节点分配一个空间用来存状态。这样做空间开不下,暴力合并信息也一定是高复杂度。所以我们只能给全局开一个空间让他来存状态。每次获取子树信息再重新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");
    }
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值