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][i−1]∗(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;
}