P5838 [USACO19DEC]Milk Visits G

发现是一道比较裸的树上莫队,于是就开始刚,然后发现好像是最难的一道题……(本题解用于作者加深算法理解,也欢迎各位的阅读)

题意

给你一棵树,树有点权,询问一条路径上是否有点权为 \(c\) 的点。

题解

我们可以比较明显地发现询问是很像莫队的询问处理的,可以 \(O(1)\) 去扩展 \(l\)\(r\) 。但是这题是树,所以我们需要引入欧拉序的概念。

欧拉序其实很像 \(dfs\) 序,但是会在出栈的时候多记录一次,我们可以利用欧拉序来将树上的路径转化为莫队需要的区间问题。

我们可以先画一张图:

其中位于节点右侧的是入栈时间,位于节点左侧的是出栈时间。

我们不妨以每一个点的入栈时间为编号,欧拉序则为:

\[1~2~3~4~4~6~6~3~9~9~11~11~2~14~15~16~16~15~19~20~20~19~14~1 \]

比如对于 \(9\) ~ \(16\) 这一条路径,我们可以用时间 \(10\) ~ \(16\) 来表示,其中出现两次的点我们不进行计算,并且还需要多加上 \(9\)\(16\)\(lca\)\(1\) ,这些可以用异或运算和特判来解决。即路径 \(x\) ~ \(y\)\(lst_x\) ~ \(fir_y\)

同时我们可以发现,如果路径上的点是为祖先关系,我们需要特殊处理,可以发现是 \(fir_x\) ~ \(fir_y\)

因此我们将所有的路径都转化为区间之后就可以用莫队离线实现了,复杂度 \(O(n\sqrt n)\) 。可是不开 \(O2\) 过不了……

以上。

代码如下:

#include<bits/stdc++.h>
using namespace std;
const int N=1e5+5,M=1e5+5;
int n,m,type[N];
struct Edge{int nxt,to;}e[N<<1];int head[N];
void add(int u,int v,int i){e[i]=Edge{head[u],v},head[u]=i;}
int fir[N],lst[N],dfn[N<<1],cnt_dfn=0;
int dep[N],fa[N][25];
void dfs(int u)
{
	dfn[++cnt_dfn]=u,fir[u]=cnt_dfn;
	for(int i=head[u];i;i=e[i].nxt)
	{
		if(e[i].to==fa[u][0]) continue;
		fa[e[i].to][0]=u;
		dep[e[i].to]=dep[u]+1;
		dfs(e[i].to);
	}
	dfn[++cnt_dfn]=u,lst[u]=cnt_dfn;
}
int lca(int u,int v)
{
	if(dep[u]>dep[v]) swap(u,v);
	for(int i=20;i>=0;--i)
	{
		if(dep[fa[v][i]]>=dep[u])
		v=fa[v][i];
	}
	if(u==v) return u;
	for(int i=20;i>=0;--i)
	{
		if(fa[u][i]!=fa[v][i])
		u=fa[u][i],v=fa[v][i];
	}
	return fa[u][0];
}
struct Query{int l,r,lca,c,id;}q[M];
int bel[N<<1],size;
bool cmp(Query a,Query b)
{
	if(bel[a.l]^bel[b.l]) return bel[a.l]<bel[b.l];
	if(bel[a.l]^1) return a.r<b.r;
	return a.r>b.r;
}
int l=1,r=0,tag[N],cnt[N],ans[M];
int main()
{
	cin>>n>>m;
	for(int i=1;i<=n;++i) scanf("%d",&type[i]);
	for(int i=1,u,v;i<n;++i)
	{
		scanf("%d%d",&u,&v);
		add(u,v,i<<1);
		add(v,u,i<<1|1);
	}
	dep[1]=1,dfs(1);
	for(int i=1;i<=20;++i)
	{
		for(int j=1;j<=n;++j)
		fa[j][i]=fa[fa[j][i-1]][i-1];
	}
	size=sqrt(n*2);
	for(int i=1,cnt=0;i<=n*2;i+=size)
	{
		++cnt;
		for(int j=i;j<min(i+size,n*2+1);++j)
		bel[j]=cnt;
	}
	for(int i=1;i<=m;++i)
	{
		scanf("%d%d%d",&q[i].l,&q[i].r,&q[i].c),q[i].id=i;
		if(fir[q[i].l]>fir[q[i].r]) swap(q[i].l,q[i].r);
		q[i].lca=lca(q[i].l,q[i].r);
		if(q[i].lca==q[i].l) q[i].l=fir[q[i].l];
		else q[i].l=lst[q[i].l];
		q[i].r=fir[q[i].r];
	}
	// for(int i=1;i<=n*2;++i)
	// printf("%d ",dfn[i]);
	// printf("\n");
	sort(q+1,q+1+m,cmp);
	// for(int i=1;i<=m;++i)
	// printf("%d %d %d\n",q[i].l,q[i].r,q[i].lca);
	for(int i=1;i<=m;++i)
	{
		while(q[i].r>r)
		{
			tag[dfn[++r]]^=1;
			if(tag[dfn[r]]) cnt[type[dfn[r]]]++;
			else cnt[type[dfn[r]]]--;
		}
		while(q[i].r<r)
		{
			tag[dfn[r]]^=1;
			if(tag[dfn[r]]) cnt[type[dfn[r--]]]++;
			else cnt[type[dfn[r--]]]--;
		}
		while(q[i].l>l)
		{
			tag[dfn[l]]^=1;
			if(tag[dfn[l]]) cnt[type[dfn[l++]]]++;
			else cnt[type[dfn[l++]]]--;
		}
		while(q[i].l<l)
		{
			tag[dfn[--l]]^=1;
			if(tag[dfn[l]]) cnt[type[dfn[l]]]++;
			else cnt[type[dfn[l]]]--;
		}
		// printf("$$$%d %d\n",l,r);
		// for(multiset<int>::iterator i=st.begin();i!=st.end();++i)
		// printf("%d ",*i);
		// printf("\n");
		ans[q[i].id]=(cnt[q[i].c]||type[q[i].lca]==q[i].c);
	}
	for(int i=1;i<=m;++i) printf("%d",ans[i]);
	printf("\n");
	return 0;
}
posted @ 2020-09-06 18:35  Point_King  阅读(117)  评论(0编辑  收藏  举报