树链剖分

杭电三:树链剖分+线段树

前置知识

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中的元素。

思路:

既然说了向上延伸,那么我先把这颗树建立起来,然后用树链把他剖分logn条线段,对于每个A中的元素,就将根节点到该点的路径上的所有点打上标记,B是同理,最后把C的子树也打上标记,问最后有三种标记的个数。

【好!到了这一步了,怎么打标记捏,】 ,用一颗线段树对于每个点维护一个三位的二进制数,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;
}
posted @   黄小轩  阅读(68)  评论(1编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示