1. 程式人生 > 實用技巧 >題解 P5838 【[USACO19DEC]Milk Visits G】

題解 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;
}