【JZOJ3360】苹果树【树上莫队】【LCA】

题目大意:

题目链接:https://jzoj.net/senior/#main/show/3360
给定一棵nn个节点的树,每个节点有一个颜色。查询两个节点之间路径上有多少种不同
的颜色,一次查询可以将一种颜色视为另一种。


思路:

考试时就写了一条链的莫队部分分+暴力的20pts20pts。然后写炸了。暴力和莫队各有一个点没拿到qwqqwq
正解是树上莫队。就是莫队的一个升级版本。
先求出这棵树的欧拉序,在欧拉序上跑莫队,注意如果这个区间涵盖了一个点偶数次,那么这个点就是不在树上这两个点的路径之间的。因为这个点肯定在这个区间内进入了一次并且出去了一次。
所以我们的莫队不能像普通的莫队一样直接写add,deladd,del,要先判断这个点的出现次数的奇偶,然后再选择add,deladd,del来修改。虽然好像没有什么区别
细节比较多,调了好久,注意细节即可。


代码:

#include <map>
#include <cmath>
#include <cstdio>
#include <string>
#include <cstring>
#include <algorithm>
using namespace std;

const int N=200010,LG=18;
int a[N],head[N],cnt[N],ans[N],f[N][LG+1],dep[N],dfn[N],s[N],t[N],pos[N],p[N];
int n,m,tot,T,sum,num;
bool find[N];

struct Ask
{
	int l,r,a,b,id;
}ask[N];

struct edge
{
	int to,next;
}e[N];

int read()
{
	int d=0;
	char ch=getchar();
	while (!isdigit(ch)) ch=getchar();
	while (isdigit(ch))
		d=(d<<3)+(d<<1)+ch-48,ch=getchar();
	return d;
}

bool cmp(Ask x,Ask y)
{
	if (pos[x.l]<pos[y.l]) return 1;
	if (pos[x.l]>pos[y.l]) return 0;
	return x.r<y.r;
}

void add(int from,int to)
{
	e[++tot].to=to;
	e[tot].next=head[from];
	head[from]=tot;
}

void dfs1(int x,int fa)
{
	dfn[++num]=x;
	s[x]=num;
	for (int i=head[x];~i;i=e[i].next)
		if (e[i].to!=fa) dfs1(e[i].to,x);
	dfn[++num]=x;
	t[x]=num;
}

void del(int x)
{
	cnt[a[x]]--;
	if (!cnt[a[x]]) sum--;
}

void add(int x)
{
	if (!cnt[a[x]]) sum++;
	cnt[a[x]]++;
}

void maintain(int x)
{
	p[x]^=1;
	if (!p[x]) del(x);
		else add(x);
}

void dfs2(int x,int fa)
{
	dep[x]=dep[fa]+1;
	f[x][0]=fa;
	for (int i=1;i<=LG;i++)
		f[x][i]=f[f[x][i-1]][i-1];
	for (int i=head[x];~i;i=e[i].next)
	{
		int v=e[i].to;
		if (v!=fa) dfs2(v,x);
	}
}

int lca(int x,int y)
{
	if (dep[x]<dep[y]) swap(x,y);
	for (int i=LG;i>=0;i--)
		if (dep[f[x][i]]>=dep[y]) x=f[x][i];
	if (x==y) return x;
	for (int i=LG;i>=0;i--)
		if (f[x][i]!=f[y][i])
		{
			x=f[x][i];
			y=f[y][i];
		}
	return f[x][0];
}

int main()
{
	freopen("data.txt","r",stdin);
	freopen("ans.out","w",stdout);
	memset(head,-1,sizeof(head));
	n=read(); m=read();
	T=sqrt(n*2);
	for (int i=1;i<=n*2;i++)
		pos[i]=(i-1)/T+1;
	for (int i=1;i<=n;i++)
		a[i]=read();
	for (int i=1,x,y;i<=n;i++)
	{
		x=read(); y=read();
		add(x,y); add(y,x);
	}
	dfs1(0,0);
	dfs2(0,0);
	for (int i=1;i<=m;i++)
	{
		ask[i].l=read(); ask[i].r=read(); 
		ask[i].a=read(); ask[i].b=read();
		ask[i].id=i;
		int LCA=lca(ask[i].l,ask[i].r);
		if (ask[i].l==LCA)
		{
			swap(ask[i].l,ask[i].r);
			ask[i].l=t[ask[i].l];
			ask[i].r=t[ask[i].r];
		}
		else if (ask[i].r==LCA)
		{
			ask[i].l=t[ask[i].l];
			ask[i].r=t[ask[i].r];
		}
		else
		{
			if (t[ask[i].l]>t[ask[i].r]) swap(ask[i].l,ask[i].r);
			ask[i].l=t[ask[i].l];
			ask[i].r=s[ask[i].r];
		}	
	}
	sort(ask+1,ask+1+m,cmp);
	int l=1,r=0;
	for (int i=1;i<=m;i++)
	{
		for (;l<ask[i].l;l++) maintain(dfn[l]);
		for (;l>ask[i].l;l--) maintain(dfn[l-1]);
		for (;r<ask[i].r;r++) maintain(dfn[r+1]);
		for (;r>ask[i].r;r--) maintain(dfn[r]);
		int x=dfn[l],y=dfn[r];
		int LCA=lca(x,y);
		if (LCA!=x && LCA!=y) add(LCA);
		if (cnt[ask[i].b] && cnt[ask[i].a] && ask[i].a!=ask[i].b) ans[ask[i].id]=sum-1;
			else ans[ask[i].id]=sum;
		if (LCA!=x && LCA!=y) del(LCA);
	}
	for (int i=1;i<=m;i++)
		printf("%d
",ans[i]);
	return 0;
}
原文地址:https://www.cnblogs.com/hello-tomorrow/p/11998088.html