#DTOJ 5245

题解

首先考虑到两点:
1.若确定 1 ≤ j ≤ i , l c a ( s j , s i ) = s i 1 \leq j \leq i,lca(s_j,s_i)=s_i 1ji,lca(sj,si)=si,那么对于 k ≥ i k \geq i ki,我们只需要考虑 s i s_i si k k k 的影响即可。也就是该性质具有无前效性
2.对于满足条件的点 { a i } \{a_i\} {ai},一定满足 a i a_i ai s 1 s_1 s1 的祖先,而且是往根节点递增的祖先。也就是该性质具有前缀性

所以,可以由这两点性质入手,由于这道题卡空间,所以我们想到一道很像的题——楼房重建。

具体地,我们把区间拆成 l o g log log 个,然后预处理线段树上的区间 l , r l,r l,r,维护以 l l l 为起点, l ≤ j ≤ i , l c a ( s j , s i ) = s i l \leq j \leq i,lca(s_j,s_i)=s_i lji,lca(sj,si)=si 的点数,以及到 r r r 之前最大(深度最小) 的 l c a lca lca 为哪个(因为 l c a lca lca 是往根递增的)。

然后去写写会发现求出答案我们只需要考虑一点:前缀 l c a lca lca k k k 的情况下,区间(线段树上的) l , r l,r l,r 中为前缀 l c a lca lca 的点数数量。形式化地,求出对于区间 l , r l,r l,r 中的一点 i i i 满足 l ≤ j ≤ i , l c a ( s j , s i , k ) = s i l \leq j \leq i,lca(s_j,s_i,k)=s_i lji,lca(sj,si,k)=si 的点数数量。

我们考虑其右区间 [ m i d + 1 , r ] [mid+1,r] [mid+1,r] l c a lca lca 最大值,若其不为 k k k 的祖先,那么显然该区间 [ l , r ] [l,r] [l,r] 内没有点能满足条件。

考虑为 k k k 祖先的情况。我们再考虑左区间 [ l , m i d ] [l,mid] [l,mid],若其 l c a lca lca 最大值不为 k k k 的祖先,那么能找到为 k k k 祖先的点显然在右区间内,递归处理右区间即可。
否则,我们递归处理左区间,求出左区间内的点数。然后我们考虑答案形式:一定是从左区间某一个点开始, l c a lca lca 递增的序列。区间 [ l , r ] [l,r] [l,r] 不考虑 k k k 的影响下,右区间 [ m i d + 1 , r ] [mid+1,r] [mid+1,r] 的答案只与左区间的最大值有关,在这里也只与左区间的最大值有关,与 k k k 无关。所以我们可以用 区间 [ l , r ] [l,r] [l,r] 的最大值减去左区间的最大值得到右区间的点数,再加上递归处理左区间得到的答案,就是区间 [ l , r ] [l,r] [l,r] k k k 的影响下的点数。

代码

#include<bits/stdc++.h>
#define LL long long
using namespace std;
inline int read(){
	int k=0,f=1;char c=getchar();
	while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
	while(c>='0'&&c<='9')k=k*10+c-'0',c=getchar();
	return k*f;
}
const int N=700005;
int n,m,q,a[N],faa;
int tot,ver[N<<1],fst[N],nxt[N<<1];
int lt[N],rt[N],dfn,zx,d[N],lc[N<<2];
int fa[N],sum[N<<2],sz[N],son[N],top[N];
long long ans;
inline void add(int x,int y){ver[++tot]=y;nxt[tot]=fst[x];fst[x]=tot;}
void dfs1(int x,int F,int dep){
	d[x]=dep;fa[x]=F;sz[x]=1;
	int mx=0;
	for(int i=fst[x];i;i=nxt[i]){
		int y=ver[i];if(y==F)continue;
		dfs1(y,x,dep+1);sz[x]+=sz[y];
		if(sz[y]>sz[mx])mx=y;
	}
	son[x]=mx;
}
void dfs2(int x,int F){
	lt[x]=++dfn;
	if(son[x])top[son[x]]=top[x],dfs2(son[x],x);
	for(int i=fst[x];i;i=nxt[i]){
		int y=ver[i];if(y==F||y==son[x])continue;
		top[y]=y;dfs2(y,x);
	}
	rt[x]=dfn;
}
inline int LCA(int x,int y){
	while(top[x]!=top[y]){
		if(d[top[x]]>=d[top[y]])x=fa[top[x]];
		else y=fa[top[y]];
	}
	return (d[x]>=d[y])?y:x;
}
inline bool pd(int x,int y){
	if(lt[y]>=lt[x]&&lt[y]<=rt[x])return true;
	return false;
}
int solve(int p,int l,int r,int mx){
	if(!mx)return sum[p];
	if(pd(a[l],mx))return sum[p];
	if(pd(lc[p],mx)){
		if(l==r){
			if(pd(lc[p],mx))return 1;
			return 0;
		}
		int mid=(l+r)>>1;
		if(pd(lc[p<<1],mx))return sum[p]-sum[p<<1]+solve(p<<1,l,mid,mx);
		return solve((p<<1)|1,mid+1,r,LCA(lc[p<<1],mx));
	}
	return 0;
}
void build(int p,int l,int r){
	if(l==r){
		lc[p]=a[l];
		sum[p]=1;
		return;
	}
	int mid=(l+r)>>1;
	build(p<<1,l,mid);build((p<<1)|1,mid+1,r);
	lc[p]=LCA(lc[p<<1],lc[(p<<1)|1]);
	sum[p]=sum[p<<1];
	sum[p]+=solve((p<<1)|1,mid+1,r,lc[p<<1]);
//	cout<<"FAQ "<<l<<" "<<r<<" "<<sum[p]<<endl;
}
inline void cale(int p,int l,int r,int L,int R){
	if(l>=L&&r<=R){
	//	
		ans+=solve(p,l,r,zx);
	//	cout<<"CALE "<<l<<" "<<r<<" "<<zx<<" "<<solve(p,l,r,zx)<<endl;
		if(zx)zx=LCA(zx,lc[p]);
		else zx=lc[p];
		return;
	}
	int mid=(l+r)>>1;
	if(L<=mid)cale(p<<1,l,mid,L,R);
	if(R>mid)cale((p<<1)|1,mid+1,r,L,R);
}
int main(){
//	freopen("tree2-5 (1).in","r",stdin);
//	freopen("tree.out","w",stdout);
	n=read();m=read();q=read();
	for(int i=1;i<n;++i){
		int x=read(),y=read();
		add(x,y);add(y,x);
	}
	for(int i=1;i<=m;++i)a[i]=read();
	dfs1(1,0,1);top[1]=1;dfs2(1,0);
	build(1,1,m);
	while(q--){
		int k=read();
		zx=0;ans=0;
		while(k--){
			int L=read(),R=read();
			cale(1,1,m,L,R);
		}
		printf("%lld\n",ans);
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值