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

博客围绕JZOJ3360题目展开,该题给定带颜色节点的树,查询两节点路径上不同颜色数量,且一次查询可将一种颜色视为另一种。作者考试时写链的莫队和暴力部分分未全拿到。正解是树上莫队,需在欧拉序上跑,根据点出现次数奇偶修改,代码细节多。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目大意:

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


思路:

考试时就写了一条链的莫队部分分+暴力的 20 p t s 20pts 20pts。然后写炸了。暴力和莫队各有一个点没拿到 q w q qwq qwq
正解是树上莫队。就是莫队的一个升级版本。
先求出这棵树的欧拉序,在欧拉序上跑莫队,注意如果这个区间涵盖了一个点偶数次,那么这个点就是不在树上这两个点的路径之间的。因为这个点肯定在这个区间内进入了一次并且出去了一次。
所以我们的莫队不能像普通的莫队一样直接写 a d d , d e l add,del add,del,要先判断这个点的出现次数的奇偶,然后再选择 a d d , d e l add,del add,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\n",ans[i]);
	return 0;
}
### 树上莫队算法概述 树上莫队算法是一种基于离线处理的查询优化技术,主要用于解决在结构上的区间统计问题。该算法的核心思想是对查询进行重新排序并利用前缀和的思想减少重复计算量。 #### 基本原理 树上莫队算法可以看作经典莫队算法的一种扩展形式。其主要目标是在一棵上高效地完成多个区间的统计操作。为了实现这一目的,通常会采用分块策略对节点编号进行划分,并按照特定顺序排列查询请求以最小化移动代价[^3]。 对于每一对起点u和终点v之间的路径长度L(u,v),可以通过两DFS遍历分别求得根到这两个点的距离d(root,u), d(root,v)以及它们最近公共祖先lca处距离d(lca,lca)=0的关系得出最终表达式:L(u,v)=d(root,u)+d(root,v)-2*d(root,lca)[^4]。 #### 应用场景 1. **动态点权更新下的最频繁颜色查询** 给定一棵带有点权的颜色标记无向连通图G=(V,E),支持两种类型的指令:“修改某顶点c_i的新色彩”或者询问某个简单路径P_{a,b}所覆盖的所有结点里出现数最多的那种色调是什么。 2. **权总和范围限制内的有效链计数** 设有N个顶点M条双向连接构成的一棵固定形态森林T加上额外K组独立条件(p,q,r),要求快速判断是否存在任意一条由p通往q方向经过不超过r单位成本约束的有效路线存在与否。 以下是Python语言版本的一个简化版树上莫队模板: ```python from collections import defaultdict, deque class TreeNode: def __init__(self): self.children = [] def preprocess_tree(node, parent=None): global depth, first_occurrence, tour_order, timer depth[node] = (depth[parent]+1) if parent is not None else 0 first_occurrence[node] = timer tour_order[timer] = node timer += 1 for child in tree[node]: if child != parent: preprocess_tree(child, node) def build_hld(): pass # Implement Heavy-Light Decomposition here as needed. n, q = map(int,input().split()) tree = {i:TreeNode() for i in range(1,n+1)} for _ in range(n-1): u,v=map(int,input().split()) tree[u].children.append(v) tree[v].children.append(u) queries = [] for _ in range(q): a,b=list(map(int,input().split())) queries.append((first_occurrence[a],first_occurrence[b])) # Sort Queries based on block strategy. sorted_queries = sorted([(block(x),y,idx) for idx,(x,y) in enumerate(queries)]) current_l,current_r = -1,-1 answer=[None]*len(sorted_queries) for qx,qy,i in sorted_queries: while current_l < qx: current_l+=1; add(tour_order[current_l]) while current_l > qx: remove(tour_order[current_l]); current_l-=1 while current_r < qy: current_r+=1; add(tour_order[current_r]) while current_r > qy: remove(tour_order[current_r]); current_r-=1 answer[i]=query_result() print("\n".join(str(ans) for ans in answer)) ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值