P9340 [JOISC 2023 Day3] Tourism 题解

发布时间 2024-01-06 08:07:45作者: Pengzt

P9340

记一下。称 \(\forall j\in[l,r]\)\(c_j\) 为关键点。

法一:

最好想的。

有个显然的结论,将所有关键点按 DFS 序排序,走过的边的数量为排序后相邻的点之间的距离。记走过的边的数量为 \(cnt_e\),则此时这些关键点所构成的虚树的大小为 \(\frac{cnt_e}{2}+1\)。这时候可以直接暴力莫队,用 set 维护 DFS 序的前驱后继即可做到 \(n\sqrt{m}\log n\)。但这样是跑不过去的。发现只需要维护前驱后继,删除用链表可以做到 \(\mathcal{O}(1)\),但不好执行加入操作。于是用一个只删不加的回滚莫队即可。

法二:

可以用 bitset 按 \(\log n\) 分块,类似于由乃救爷爷。但我不会 。

法三:

还有一种计算点集大小的方法,用数据结构维护一下,直接标记所有关键点走到某一个节点所经过的边,然后查询被标记的边的数量,这样配合回滚莫队也可以做到 \(\mathcal{O}(n\sqrt{m}\log^2n)\)\(2\log\) 是因为用的是树链剖分。是不能通过的。

想一下为什么会这么麻烦。发现是因为必须要钦定某一个特定的点与其它点的路径标记。考虑能否将这个点固定下来。钦定这个点为 \(1\),并让 \(1\) 作为根,看看会有什么变化。令 \(anc=\text{lca}\{c_j\}(j\in [l,r])\),发现多出的答案就是 \(dep_{anc}-dep_1\),其中 \(\text{lca}\{S\}\) 可以用 ST 表处理出。这样就可以不用回滚了。现在已经简单了不少,考虑如何去掉根号。

考虑离线后按 \(r\) 排序,以时间为值进行覆盖,则此时 \(anc\) 内值 \(\ge l\) 的边就一定会被统计。这时 \(anc\) 的子树外的边不会统计,但是这些边被被赋的值一定是当前最大的,直接减去即可。这是转化为了路径赋值 \((1,u)\),全局查询值在某个区间的数的个数。

暴力的维护是简单的,直接上分块即可。

但这样的时间复杂度依然很高。想想区间赋值,可以直接用 ODT。这里 ODT 的复杂度显然是可以保证的,但是每次查询暴力扫一遍 \(1\sim m\) 的话会炸,再用一棵 BIT 维护值域即可。

有更优的实现方式:参考 Leasier 的思路,发现是对每条重链前缀赋值,可以使用 vector 替代 ODT。

时间复杂度:\(\mathcal{O}((n+q)\log^2n)\),这里认为 \(n,m\) 同阶。

空间复杂度:\(\mathcal{O}(n\log n)\)

代码:

const int N=1e5+10;
int n,m,Q;
int a[N],st[18][N],log_2[N],ans[N];
struct BIT{
	int v[N];
	void add(int x,int y){assert(x);for(;x<=m;x+=x&-x)v[x]+=y;}
	int ask(int x){
		int res=0;
		for(;x;x-=x&-x)res+=v[x];
		return res;
	}
} T;
int head[N],cnt_e=1;
struct edge{int v,nxt;} e[N<<1];
void adde(int u,int v){
	e[++cnt_e]=(edge){v,head[u]};
	head[u]=cnt_e;
}
int dfc,dfn[N],rdfn[N],sz[N],hv[N],d[N],fa[N],top[N];
void dfs1(int u,int fath){
	sz[u]=1,d[u]=d[fath]+1,fa[u]=fath;
	for(int i=head[u],v;i;i=e[i].nxt)if((v=e[i].v)!=fath){
		dfs1(v,u);
		sz[u]+=sz[v];
		if(sz[v]>sz[hv[u]])hv[u]=v;
	}
}
void dfs2(int u,int tp){
	rdfn[dfn[u]=++dfc]=u,top[u]=tp;
	if(!hv[u])return;
	dfs2(hv[u],tp);
	for(int i=head[u],v;i;i=e[i].nxt)if((v=e[i].v)!=hv[u]&&v!=fa[u])dfs2(v,v);
}
int glca(int u,int v){
	while(top[u]!=top[v]){
		if(d[top[u]]<d[top[v]])swap(u,v);
		u=fa[top[u]];
	}
	if(d[u]<d[v])swap(u,v);
	return v;
}
int qlca(int l,int r){
	int k=log_2[r-l+1];
	return glca(st[k][l],st[k][r-(1<<k)+1]);
}
vector<int> vec[N];pii q[N];
struct ODT{
	int l,r;mutable int v;
	ODT(const int &tl,const int &tr,const int &tv){l=tl;r=tr;v=tv;}
	friend bool operator<(ODT a,ODT b){return a.l<b.l;}
};
set<ODT> odt;
typedef set<ODT>::iterator iter;
iter split(int x){
	iter it=--odt.upper_bound((ODT){x,0,0});
	if(it->l==x)return it;
	assert(it!=odt.end());
	int l=it->l,r=it->r,v=it->v;
	odt.erase(it);
	odt.insert((ODT){l,x-1,v});
	return odt.insert((ODT){x,r,v}).first;
}
void assign(int l,int r,int tim){
	iter itr=split(r+1),itl=split(l);
	for(iter it=itl;it!=itr;it++)T.add(m-it->v+1,-(it->r-it->l+1));
	odt.erase(itl,itr);
	odt.insert((ODT){l,r,tim});
	T.add(m-tim+1,r-l+1);
}
void upd(int u,int tim){
	while(u){
		assign(dfn[top[u]],dfn[u],tim);
		u=fa[top[u]];
	}
}
int main(){
	scanf("%d%d%d",&n,&m,&Q);odt.insert((ODT){1,n,0});
	for(int i=1,u,v;i<n;i++)scanf("%d%d",&u,&v),adde(u,v),adde(v,u);
	for(int i=1;i<=m;i++)scanf("%d",&a[i]),st[0][i]=a[i];
	dfs1(1,0),dfs2(1,1);
	for(int i=2;i<=m;i++)log_2[i]=log_2[i>>1]+1;
	for(int i=1;i<=17;i++)for(int j=1;j+(1<<i)-1<=m;j++)st[i][j]=glca(st[i-1][j],st[i-1][j+(1<<i-1)]);
	for(int i=1;i<=Q;i++)cin>>q[i].first>>q[i].second,vec[q[i].second].eb(i),ans[i]=-d[qlca(q[i].first,q[i].second)]+d[1];
	for(int i=1;i<=m;i++){
		upd(a[i],i);
		for(int j:vec[i])ans[j]+=T.ask(m-q[j].first+1);
	}
	for(int i=1;i<=Q;i++)printf("%d\n",ans[i]);
	return 0;
}