【树上莫队算法】苹果树

【题目描述】
神犇家门口种了一棵苹果树。苹果树作为一棵树,当然是呈树状结构,每根树枝连接两个苹果,每个苹果都可以沿着一条由树枝构成的路径连到树根,而且这样的路径只存在一条。由于这棵苹果树是神犇种的,所以苹果都发生了变异,变成了各种各样的颜色。我们用一个1到N之间的正整数来表示一种颜色。树上一共有N个苹果。每个苹果都被编了号码,号码为一个1到N之间的正整数。我们用0代表树根。只会有一个苹果直接连到树根。

有许许多多的人来神犇家里膜拜神犇。可神犇可不是随便就能膜拜的。前来膜拜神犇的人需要正确回答一个问题,才能进屋膜拜神犇。这个问题就是,从树上编号为u的苹果出发,由树枝走到编号为v的苹果,路径上经过的苹果一共有多少种不同的颜色(包括苹果u和苹果v的颜色)?不过神犇注意到,有些来膜拜的人患有色盲症。具体地说,一个人可能会认为颜色a就是颜色b,那么他们在数苹果的颜色时,如果既出现了颜色a的苹果,又出现了颜色b的苹果,这个人只会算入颜色b,而不会把颜色a算进来。

神犇是一个好人,他不会强人所难,也就会接受由于色盲症导致的答案错误(当然答案在色盲环境下也必须是正确的)。不过这样神犇也就要更改他原先数颜色的程序了。虽然这对于神犇来说是小菜一碟,但是他想考验一下你。你能替神犇完成这项任务吗?

【输入描述】
输入第一行为两个整数N和M,分别代表树上苹果的个数和前来膜拜的人数。

接下来的一行包含N个数,第i个数代表编号为i的苹果的颜色〖col〗_i。

接下来有N行,每行包含两个数x和y,代表有一根树枝连接了苹果x和y(或者根和一个苹果)。

接下来有M行,每行包含四个整数u、v、a和b,代表这个人要数苹果u到苹果v的颜色种数,同时这个人认为颜色a就是颜色b。如果a=b=0,则代表这个人没有患色盲症。

【输出描述】
输出一共M行,每行仅包含一个整数,代表这个人应该数出的颜色种数。

【思路】

显然地,这是一道树上莫队的题。
对于树上的莫队,为了把树上的操作转换为一般莫队算法中的序列操作,我们引入一个叫做括号序的概念,和dfs序差不多,只是我们需要记录进入每个节点和离开当前节点的时间戳。
代码:(这里的括号序和lca的预处理写在一起的)

void dfs(int u)
{
	for(int re i=1;(1<<i)<=dep[u];i++)
		fa[u][i]=fa[fa[u][i-1]][i-1];
	st[u]=++tot;
	rev[tot]=u;
	for(int re i=f[u];i;i=nxp[i])
	{
		int v=e[i].v;
		if(!st[v])
		{
			dep[v]=dep[u]+1;
			fa[v][0]=u;
			dfs(v);
		}
	}
	ed[u]=++tot;
	rev[tot]=u;
}

代码中的rev数组是为了找到括号序对应的原树的节点,st和ed数组就是开始搜索子树和搜索子树结束的时间戳,。简单分析一下,我们可以发现:对于节点u和v,如果它们的lca不是u也不是v,u到v路径上的点(不含lca)在括号序中的区间[ed[u],st[v]] (ed[u]<st[v])只会出现一次,如果其中(比如u)一个是它们的lca,那么它们路径上的点(不含lca)在区间[st[u],st[v]] (st[u]<st[v])中只会出现一次。对于其它出现了两次的点或者没有出现的点,就不在路径上面,我们只需要转移时判断当前节点是否在当前区间内即可插入删除。当然,区间内不含lca,故我们还需要把lca单独统计进答案中,回答完当前询问再删除即可。于是我们现在就把树上的路经询问变为了普通序列上的询问,就可以使用分块加莫队算法了。(是不是听起来很简单呀!!)
参考代码:

#include<cstdio>
#include<iostream>
#include<queue>
#include<algorithm>
#include<cstring>
#include<string>
#include<vector>
#include<cmath>
#define re register
using namespace std;
int c,d,n,m,k;
int idx[200001];
struct node{
	int u,v;
}e[200001];
int f[200001];
int nxp[200001];
int cnt=0;
struct que{
	int l,r;//rev
	int ans;
	int a,b;
	int id;
	int needlc;
	int lc;
}q[200001];
inline bool cmp1(que a,que b)
{
	if(idx[a.l]==idx[b.l])return a.r<b.r;
	return idx[a.l]<idx[b.l];
}
inline bool cmp2(que a,que b){return a.id<b.id;}
int val[200001];
inline void add_e(int u,int v)
{
	e[++cnt].u=u;
	e[cnt].v=v;
	nxp[cnt]=f[u];
	f[u]=cnt;
}
int root=0;
int st[100001];
int ed[100001],tot=0;
int rev[200001];
int fa[200001][20];
int dep[200001];
void dfs(int u)
{
	for(int re i=1;(1<<i)<=dep[u];i++)
		fa[u][i]=fa[fa[u][i-1]][i-1];
	st[u]=++tot;
	rev[tot]=u;
	for(int re i=f[u];i;i=nxp[i])
	{
		int v=e[i].v;
		if(!st[v])
		{
			dep[v]=dep[u]+1;
			fa[v][0]=u;
			dfs(v);
		}
	}
	ed[u]=++tot;
	rev[tot]=u;
}
int tmp[200001];
int l,r;
int ans=1;
int use[200001];
inline void add(int i)
{
	tmp[val[rev[i]]]++;
	if(tmp[val[rev[i]]]==1)ans++;
}
inline void del(int i)
{
	tmp[val[rev[i]]]--;
	if(tmp[val[rev[i]]]==0)ans--;
}
int lca(int a,int b)
{
	if(dep[a]<dep[b])swap(a,b);
	int t=dep[a]-dep[b];
	for(int re i=0;(1<<i)<=t;i++)
		if(t&(1<<i))a=fa[a][i];
	if(a==b)return a;
	for(int re i=18;i>=0;i--)
	{
		if(fa[a][i]!=fa[b][i])
		{
			a=fa[a][i];
			b=fa[b][i];
		}
	}
	return fa[a][0];
}
inline void solve()
{
	sort(q+1,q+m+1,cmp1);
	l=1,r=1;
	tmp[val[rev[1]]]++;
	use[rev[1]]++;
	for(int re i=1;i<=m;i++)
	{
		while(q[i].r>r)
		{
			r++;
			if(!use[rev[r]])add(r);
			else del(r);
			use[rev[r]]++;
		}
		while(q[i].l<l)
		{
			l--;
			if(!use[rev[l]])add(l);
			else del(l);
			use[rev[l]]++;
		}
		while(q[i].l>l)
		{
			if(use[rev[l]]==1)del(l);
			else add(l);
			use[rev[l]]--;
			l++;
		}
		while(q[i].r<r)
		{
			if(use[rev[r]]==1)del(r);
			else add(r);
			use[rev[r]]--;
			r--;
		}
		add(st[q[i].lc]);
		if((q[i].a==0 &&tmp[q[i].a])||(q[i].b==0 &&tmp[q[i].b]))while(1)1;
		if(tmp[q[i].a]&&tmp[q[i].b]&&q[i].a!=q[i].b)q[i].ans=ans-1;
		else q[i].ans=ans;
		del(st[q[i].lc]);
		
	}
	sort(q+1,q+m+1,cmp2);
}
int main()
{
	scanf("%d%d",&n,&m);
	k=sqrt((n*2));
	int len=n<<1;
	for(int re i=1;i<=n;i++)
		scanf("%d",&val[i]);
	for(int re i=1;i<=len;i++)idx[i]=(i-1)/k+1;
	for(int re i=1;i<=n;i++)
	{
		int a,b;
		scanf("%d%d",&a,&b);
		if((a==0)||(b==0))
			continue;
		add_e(a,b);
		add_e(b,a);
	}
	dep[1]=1;
	dfs(1);
	for(int re i=1;i<=m;i++)
	{
		int a,b;
		scanf("%d%d%d%d",&a,&b,&q[i].a,&q[i].b);
		if(st[a]>st[b])swap(a,b);
		int lc=lca(a,b);
		if(a==lc)
		{
			q[i].l=st[a];
			q[i].r=st[b];
		}
		else
		if(b==lc)
		{
			q[i].l=st[b];
			q[i].r=st[a];
		}
		else
		{
			q[i].l=ed[a];
			q[i].r=st[b];
		}
		q[i].id=i;
		q[i].lc=lc;
	}
	solve();
	for(int re i=1;i<=m;i++)
		printf("%d\n",q[i].ans);
}
  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值