树链剖分
杭电三:树链剖分+线段树
前置知识
DFS序:
在处理一棵树的时候先进行一遍DFS,把树上问题转化为链上问题。
重儿子
树链剖分在简单的DFS序上增加了一个定义:重儿子:一颗子数中size最大的一个分支,称作这个父节点的重儿子,然后在进行DFS序的形成的过程中,优先处理重儿子的那一条链。
// 判断重儿子
void dfs1(int u,int p,int deep)
{
sz[u]=1;fa[u]=p;dp[u]=deep;
for(int i=h[u];~i;i=ne[i])
{
int j=e[i];
if(j==p)continue;
dfs1(j,u,deep+1);
sz[u]+=sz[j];
if(sz[son[u]]<sz[j])son[u]=j;
}
}
//搞出DFS序
void dfs2(int u,int p)
{
id[u]=++cnt;nw[cnt]=w[u];top[u]=p;
if(!son[u])return ;
dfs2(son[u],p);
for(int i=h[u];~i;i=ne[i])
{
int j=e[i];
if(j==fa[u]||j==son[u])continue;
dfs2(j,j);
}
}
线段树感觉没啥好说的
题目:Static Query on Tree
O.o 脑淤血日常,想到了正解没敢写。
题意:
首先,给定一棵树,然后三个集合:A B C,让A,B中的点向上延伸得到集合D,最后在所有的C的集合的子树中,有多少D中的元素。
思路:
既然说了向上延伸,那么我先把这颗树建立起来,然后用树链把他剖分
【好!到了这一步了,怎么打标记捏,】 ,用一颗线段树对于每个点维护一个三位的二进制数,sum[3]表示当前二进制数的第i为的状态,懒标记也就四种,123对应ABC的标记,4对应清除,方便最后clear。
基本思路就是这样,细节在代码中以注释的形式展出。
struct T
{
int l,r;
int sum[4];
int lz[4];
//清除操作
void clear()
{
for(int i=0;i<4;i++)lz[i]=sum[i]=0;
}
}tr[4*N];
vector<int>v[N];
int cnt,id[N],sz[N];
int dp[N],fa[N],top[N],son[N];
int n,m;
//获得每个点的重儿子
void dfs1(int u,int p,int deep)
{
sz[u]=1;fa[u]=p;dp[u]=deep;
for(int j:v[u])
{
if(j==p)continue;
dfs1(j,u,deep+1);
sz[u]+=sz[j];
if(sz[son[u]]<sz[j])son[u]=j;
}
}
//获得每个点的DFS序号
void dfs2(int u,int p)
{
id[u]=++cnt;top[u]=p;
if(!son[u])return ;
dfs2(son[u],p);
for(int j:v[u])
{
if(j==fa[u]||j==son[u])continue;
dfs2(j,j);
}
}
//统计当前区间每一位二进制位的数量,
void pushup(int u)
{
for(int i=0;i<3;i++)tr[u].sum[i]=(tr[u<<1].sum[i]+tr[u<<1|1].sum[i]);
}
// pushdown 操作
/*
注意顺序:
1.如果他被清空了,优先将所有的清空,
2.他的第二位建立在第一位有的基础上,也可以反过来,只是规定一个顺序,防止两位只有一位出现。
3。第三位出现在第二位的基础上,也是同理/
*/
void wk(T &t,int lz)
{
if(lz==3)
{
t.clear();
t.lz[3]=1;
}
else if(lz==0)
{
t.sum[0]=t.r-t.l+1;
t.lz[0]=1;
}
else if(lz==1)
{
t.sum[1]=t.sum[0];
t.lz[1]=1;
}
else if(lz==2)
{
t.sum[2]=t.sum[1];
t.lz[2]=1;
}
}
//优先处理!clear操作!!!!
void pushdown(int u)
{
if(tr[u].lz[3])
{
wk(tr[u<<1],3);
wk(tr[u<<1|1],3);
tr[u].lz[3]=0;
}
if(tr[u].lz[0])
{
wk(tr[u<<1],0);
wk(tr[u<<1|1],0);
tr[u].lz[0]=0;
}
if(tr[u].lz[1])
{
wk(tr[u<<1],1);
wk(tr[u<<1|1],1);
tr[u].lz[1]=0;
}
if(tr[u].lz[2])
{
wk(tr[u<<1],2);
wk(tr[u<<1|1],2);
tr[u].lz[2]=0;
}
}
// 更新没什么好说的。
void update(int u,int l,int r,int k)
{
if(tr[u].l>=l&&r>=tr[u].r)
{
wk(tr[u],k);
return;
}
pushdown(u);
int mid=tr[u].l+tr[u].r>>1;
if(mid>=l)update(u<<1,l,r,k);
if(r>mid)update(u<<1|1,l,r,k);
pushup(u);
}
//build 也是基本的build
void build(int u,int l,int r)
{
tr[u]={l,r};
if(l==r)
{
return;
}
int mid=l+r>>1;
build(u<<1,l,mid);
build(u<<1|1,mid+1,r);
pushup(u);
}
//板子; 每次都往上跳,保证了数量不超过logn条。
void update_path(int u,int v,int k)
{
while(top[u]!=top[v])
{
if(dp[top[u]]<dp[top[v]])swap(u,v);
update(1,id[top[u]],id[u],k);
u=fa[top[u]];
}
if(dp[u]<dp[v])swap(u,v);
// cout<<id[v]<<" "<<id[u]<<endl;
update(1,id[v],id[u],k);
}
void solve()
{
int q;
cin>>n>>q;
for(int i=1;i<=n;i++)v[i].clear();
for(int i=2;i<=n;i++)
{
int x;cin>>x;
v[x].ps(i);
}
dfs1(1,-1,1);
dfs2(1,-1);
build(1,1,n);
while(q--)
{
int A,B,C;
cin>>A>>B>>C;
while(A--)
{
int x;cin>>x;
update_path(1,x,0);
}
// cout<<tr[1].sum[0]<<endl;
while(B--)
{
int x;cin>>x;
update_path(1,x,1);
}
while(C--)
{
int x;cin>>x;
update(1,id[x],id[x]+sz[x]-1,2);
}
cout<<tr[1].sum[2]<<endl;
tr[1].clear();
tr[1].lz[3]=1;//这一步相当于直接清空所有的情况咯。
}
}
signed main()
{
kd;
int _;_=1;
cin>>_;
while(_--)solve();
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】