2022“杭电杯”中国大学生算法设计超级联赛(2)A.Static Query on Tree(树链剖分)

2022“杭电杯”中国大学生算法设计超级联赛(2)A.Static Query on Tree(树链剖分)

题目链接:Static Query on Tree

题意:给定一颗根为 1 1 1的树,规定所有边都指向根方向,给定m次操作,每次给定三个集合的点,A,B,C,询问树中有多少个点可以同时被A和B中的某个点到达,并且可以走到C集合中的某个点。
题解:官方给的标程只有解法二,这里我就详细说说解法一:树链剖分,由于m次询问中A,B,C集合的点数总和 ≤ 2 e 5 \leq 2e5 2e5,所有我们可以用每次标记下从A,B,中点到达终点到达路径,再标记下C中点的子树,统计下一共有多少个点同时被三种点标记,这里我用 a [ r t ] [ i ] a[rt][i] a[rt][i], i i i的取值为0,1,2分别表示当前区间中有多少个点被A标记,多少个点被A,B同时标记,多少个点被A,B,C同时标记,用懒标记 l a z y [ r t ] [ i ] lazy[rt][i] lazy[rt][i] i i i的取值为0,1,2,分别表示当前区间被 A , B , C A,B,C A,B,C 0 / 1 0/1 0/1,那么可以推出: a [ r t ] [ i ] = a [ r t ] [ i − 1 ] ∗ ( l a z y [ r t ] [ i ] ) a[rt][i]=a[rt][i-1]*(lazy[rt][i]) a[rt][i]=a[rt][i1](lazy[rt][i]),表示当前区间有i种标记的点的数量可以通过有i-1种标记的点的数量来确定,剩下的就是经典的树链剖分板子了。
不懂和细节可以见代码(注:代码中注释为我板子中的注释,和本题没啥关系,千万别被误导~~):

#include<iostream>
#include<stack>
#include<list>
#include<set>
#include<vector>
#include<algorithm>
#include<math.h>
#include<numeric>
#include<map>
#include<cstring>
#include<queue>
#include<iomanip>
#include<cmath>
#include<queue>
#include <bitset>
#include<unordered_map>
	#ifndef local
	#define endl '\n'
#endif */
#define mkp make_pair
using namespace std;
using std::bitset;
typedef long long ll;
typedef long double ld;
const int inf=0x3f3f3f3f;
const ll MAXN=4e5+10;
const ll N=2e5+100;
const ll mod=1e9+7;
const ll hash_p1=1610612741;
const ll hash_p2=805306457;
const ll hash_p3=402653189;
//-----------------------------------------------------------------------------------------------------------------*/
// ll head[MAXN],net[MAXN],to[MAXN],edge[MAXN]/*流量*/,cost[MAXN]//费用;
/* 
void add(ll u,ll v,ll w,ll s){
	to[++cnt]=v;net[cnt]=head[u];edge[cnt]=w;cost[cnt]=s;head[u]=cnt;
	to[++cnt]=u;net[cnt]=head[v];edge[cnt]=0;cost[cnt]=-s;head[v]=cnt;
}
struct elemt{
	int p,v;
};
struct comp{
	public:
		bool operator()(elemt v1,elemt v2){
			return v1.v<v2.v;
		}
};
-----------------------------------
求[1,MAXN]组合式和逆元 
ll fac[MAXN+100],inv[MAXN+100];
ll mi(ll a,ll b){
	ll res=1;
	while(b){
		if(b%2){
			res=res*a%mod;
		}
		a=a*a%mod;
		b/=2;
	}
	return res;
}
void init(){
	fac[0]=1;inv[0]=1;
	for(int i=1;i<=MAXN;i++){
		fac[i]=(fac[i-1]*i)%mod;
		inv[i]=mi(fac[i],mod-2);
	}
}
ll C(int m,int n){//组合式C(m,n); 
	if(!n){
		return 1;
	}
	return fac[m]*(inv[n]*inv[m*-n]%mod)%mod;
}
---------------------------------
 unordered_map<int,int>mp;
//优先队列默认小顶堆 , greater<int> --小顶堆  less<int> --大顶堆  
priority_queue<elemt,vector<elemt>,comp>q;  
	set<int>::iterator it=st.begin();
*/
// vector<vector<int>>edge; 二维虚拟储存坐标 
//-----------------------------------------------------------------------------------------------------------------*/
  //unordered_map<ll,int>mp;
int n,m,r;//节点数 操作数 根节点序号 取模数 
int head[MAXN*2],net[MAXN*2],to[MAXN*2],e;//链式前向星 
void add(int u,int v){//建边 
	to[++e]=v;net[e]=head[u];head[u]=e;
}
ll w[MAXN],id[MAXN],wt[MAXN];//w为初始编号的点对应权值,id下标i重新编号后的下标, wt为重新编号后对应的权值 
int son[MAXN],fa[MAXN],dep[MAXN],siz[MAXN],top[MAXN];//son为重儿子编号,fa为父亲节点编号,dep为深度,size为子树大小,top为当前链顶端 
int cnt;//记录新编号 
int a[MAXN*4][3],lazy[MAXN*4][3];//线段树数组  lazy标记 
ll res=0;//记录答案 
void dfs1(int x,int f,int deep){//当前节点 父亲节点, deep深度 
	dep[x]=deep;
	fa[x]=f;
	siz[x]=1;
	int maxson=-1;//记录重儿子的子树大小 
	for(int i=head[x];i;i=net[i]){
		int v=to[i];
		if(v==f){
			continue;
		}
		dfs1(v,x,deep+1);//dfs儿子
		siz[x]+=siz[v];
		if(siz[v]>maxson){
			son[x]=v;
			maxson=siz[v];
		} 
	} 
}
void dfs2(int x,int topf){//当前节点 当前链最顶端节点 
	id[x]=++cnt;//生产新编号 
	wt[cnt]=w[x];//将权值赋给新编号
	top[x]=topf;//记录当前点所在链顶端
	if(!son[x]){
		return ;//没有可走点了(无重儿子) 
	} 
	dfs2(son[x],topf);//先处理重儿子,重儿子的链顶为当前点的链顶 
	for(int i=head[x];i;i=net[i]){//后处理轻儿子 
		int v=to[i];
		if(v==fa[x]||v==son[x]){
			continue;
		}
		dfs2(v,v);//轻儿子的链从自己开始 
	} 
}
//----------------------------------------------------- 线段区间修改+维护区间和 
void pushdown(int rt,int l,int r){//线段树区间求和的lazy标记下沉 
	for(int i=0;i<3;i++){
		if(lazy[rt][i]==-1){
			continue;
		}
		lazy[2*rt][i]=lazy[rt][i];
		lazy[2*rt+1][i]=lazy[rt][i];
		int mid=(l+r)/2;
		if(i==0){/
			a[2*rt][i]=lazy[rt][i]*(mid-l+1);
			a[2*rt+1][i]=lazy[rt][i]*(r-mid);
		}
		else{
			a[2*rt][i]=lazy[rt][i]*(a[2*rt][i-1]);
			a[2*rt+1][i]=lazy[rt][i]*(a[2*rt+1][i-1]);
		}
		lazy[rt][i]=-1;
	}
}
void pushin(int rt){
	for(int i=0;i<3;i++){
		a[rt][i]=a[2*rt][i]+a[2*rt+1][i];
	}
}
void build(int rt,int l,int r){//建树 
	if(l==r){
		a[rt][0]=0;//只被A的路径覆盖的
		a[rt][1]=0;//被A,B覆盖的
		a[rt][2]=0;//被A,B,C覆盖的
		lazy[rt][0]=-1;//分别代表A,B,C将该段区间全赋值为0/1
		lazy[rt][1]=-1;
		lazy[rt][2]=-1;
		return ;
	}
	int mid=(l+r)/2; 
	build(2*rt,l,mid);
	build(2*rt+1,mid+1,r);
	pushin(rt);
}
void update(int rt,int l,int r,int L,int R,int k,int idx){
	if(L<=l&&r<=R){
		lazy[rt][idx]=k;
		if(idx==0){
			a[rt][0]=(r-l+1)*k;
		}
		else{
			a[rt][idx]=a[rt][idx-1]*k;
		}
	}
	else{
		pushdown(rt,l,r);
		int mid=(l+r)/2;
		if(L<=mid){
			update(2*rt,l,mid,L,R,k,idx);
		}
		if(R>mid){
			update(2*rt+1,mid+1,r,L,R,k,idx);
		}
		pushin(rt);
	}
}
void query(int rt,int l,int r,int L,int R){
	if(L<=l&&r<=R){
		res+=a[rt][2];
		//cout<<"l="<<l<<" r="<<r<<" 0="<<a[rt][0]<<" 1="<<a[rt][1]<<" 2="<<a[rt][2]<<endl;
		res%=mod;
		return;
	}
	else{
		pushdown(rt,l,r);
		int mid=(l+r)/2;
		if(L<=mid){
			query(2*rt,l,mid,L,R);
		}
		if(R>mid){
			query(2*rt+1,mid+1,r,L,R);
		}
	}
}
//----------------------------------------------
int qRange(int x,int y){//求路径x-y节点权值和(==路上所需要跳过的链的区间和+最后共同到达的链中二者相差的区间和)
	int ans=0;
	//--------------------------------------------------------------------这里面为二者最短路径 
	while(top[x]!=top[y]){
		if(dep[top[x]]<dep[top[y]]){
			swap(x,y);//把x点改为所在链顶端的深度更深的那个点
		}
		res=0;
		query(1,1,n,id[top[x]],id[x]);//ans加上x点到x所在链顶端 这一段区间的点权和
		ans+=res;
		ans%=mod;
		x=fa[top[x]];//把x跳到x所在链顶端的那个点的上面一个点
	} 
	if(dep[x]>dep[y]){
		swap(x,y); //把x改成更深的那个节点 ,因为同一链内编号有序,按dfs序,所以深度大的编号大, 
	}
	res=0;
	query(1,1,n,id[x],id[y]);//这时再加上此时两个点的区间和即可
	//---------------------------------------------------------------------
	ans+=res;
	return ans%mod;
}
void upRange(int x,int y,int k,int idx){//路径同上,只不过这里为区间修改 
	k%=mod;
	while(top[x]!=top[y]){
		if(dep[top[x]]<dep[top[y]]){
			swap(x,y);
		}
		update(1,1,n,id[top[x]],id[x],k,idx); 
		x=fa[top[x]];
	}
	if(dep[x]>dep[y]){
		swap(x,y);
	}
	update(1,1,n,id[x],id[y],k,idx);
}
int qSon(int x){
	res=0;
	query(1,1,n,id[x],id[x]+siz[x]-1);//因为子树内点有序(dfs序),所以左右端点可确定 
	return res;
}
void upSon(int x,int k,int idx){//路径同上 ,只不过这里为区间修改 
	k%=mod;
	update(1,1,n,id[x],id[x]+siz[x]-1,k,idx);
}
int dx[N][3];
int num[3];
int main(){
/*cout<<setiosflags(ios::fixed)<<setprecision(8)<<ans<<endl;//输出ans(float)格式控制为8位小数(不含整数部分)*/
/*cout<<setprecision(8)<<ans<<endl;//输出ans(float)格式控制为8位小数(含整数部分)*/
	ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);//同步流
int t;
cin>>t;
while(t--){
	cin>>n>>m;
	r=1;
	e=0;
	memset(head,0,sizeof(head));
	for(int i=2;i<=n;i++){//建边 
		int u,v;
		cin>>v;
		u=i;
		add(u,v);
		add(v,u);
	}
	dfs1(r,0,1);
	dfs2(r,r);
	build(1,1,n);//建树
	while(m--){
		cin>>num[0]>>num[1]>>num[2];
		for(int i=0;i<3;i++){
			for(int j=1;j<=num[i];j++){
				cin>>dx[j][i];
				if(i!=2){
					upRange(1,dx[j][i],1,i);
				}
				else{
					upSon(dx[j][i],1,i);
				}
			}
		}
		cout<<qSon(1)<<endl;
		upSon(1,0,0);//清空
		upSon(1,0,1);
		upSon(1,0,2);
	} 
}
	return 0;
}
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值