题目链接
给定一棵 N 个节点的树,节点编号从 1 到 N,每个节点都有一个整数权值。
现在,我们要进行 M 次询问,格式为 u v
,对于每个询问你需要回答从 u 到 v 的路径上(包括两端点)共有多少种不同的点权值。
输入格式
第一行包含两个整数 N,M。
第二行包含 N 个整数,其中第 i 个整数表示点 i 的权值。
接下来 N−1 行,每行包含两个整数 x,y,表示点 x 和点 y 之间存在一条边。
最后 M 行,每行包含两个整数 u,v,表示一个询问。
输出格式
共 M 行,每行输出一个询问的答案。
数据范围
1≤N≤40000,
1≤M≤105,
1≤x,y,u,v≤N,
各点权值均在 int 范围内。
输入样例:
输出样例:
解题思路
树上莫队,lca
先将整棵树的欧拉序求出来,记录每个点第一次出现的位置 first[i] 和最后一次出现的位置 last[i],然后观察树中的路径 [l,r](first[l]<first[r]) 可以发现两种情况:
- 如果路径是一条从上往下的直链,则其所有点对应欧拉序中 first[l] 到 first[r] 中出现一次的点
- 否则其所有点对应欧拉序中 first[l] 到 last[r] 中出现一次的点加上 lca(l,r)
理解一下会发现的确这样,然后问题就转化为普通莫队问题了
代码
// Problem: 树上计数2
// Contest: AcWing
// URL: https://www.acwing.com/problem/content/2536/
// Memory Limit: 64 MB
// Time Limit: 1000 ms
//
// Powered by CP Editor (https://cpeditor.org)
// %%%Skyqwq
//
using namespace std;
typedef long long LL;
typedef pair<int, int> PII;
typedef pair<LL, LL> PLL;
template <typename T> bool chkMax(T &x, T y) { return (y > x) ? x = y, 1 : 0; }
template <typename T> bool chkMin(T &x, T y) { return (y < x) ? x = y, 1 : 0; }
template <typename T> void inline read(T &x) {
int f = 1; x = 0; char s = getchar();
while (s < '0' || s > '9') { if (s == '-') f = -1; s = getchar(); }
while (s <= '9' && s >= '0') x = x * 10 + (s ^ 48), s = getchar();
x *= f;
}
const int N=1e5+5;
int n,m,cnt[N],st[N],len,w[N],ans[N];
int seq[N],first[N],last[N],top;
int f[N][20],d[N],t;
vector<int> nums,adj[N];
struct query
{
int id,l,r,p;
}q[N];
void dfs(int x,int fa)
{
seq[++top]=x;
first[x]=top;
for(int y:adj[x])
{
if(y==fa)continue;
dfs(y,x);
}
seq[++top]=x;
last[x]=top;
}
void bfs()
{
queue<int> q;
q.push(1);
memset(d,0x3f,sizeof d);
d[0]=0,d[1]=1;
while(q.size())
{
int x=q.front();
q.pop();
for(int y:adj[x])
{
if(d[y]>d[x]+1)
{
d[y]=d[x]+1;
f[y][0]=x;
for(int i=1;i<=t;i++)f[y][i]=f[f[y][i-1]][i-1];
q.push(y);
}
}
}
}
int lca(int x,int y)
{
if(d[x]<d[y])swap(x,y);
for(int i=t;i>=0;i--)
if(d[f[x][i]]>=d[y])x=f[x][i];
if(x==y)return x;
for(int i=t;i>=0;i--)
if(f[x][i]!=f[y][i])x=f[x][i],y=f[y][i];
return f[x][0];
}
int get(int x)
{
return x/len;
}
void add(int x,int &res)
{
st[x]^=1;
if(st[x])
{
if(!cnt[w[x]])res++;
cnt[w[x]]++;
}
else
{
cnt[w[x]]--;
if(!cnt[w[x]])res--;
}
}
int main()
{
cin>>n>>m;
t=__lg(n);
for(int i=1;i<=n;i++)cin>>w[i],nums.pb(w[i]);
sort(nums.begin(),nums.end());
nums.erase(unique(nums.begin(),nums.end()),nums.end());
for(int i=1;i<=n;i++)w[i]=lower_bound(nums.begin(),nums.end(),w[i])-nums.begin();
for(int i=1;i<n;i++)
{
int x,y;
cin>>x>>y;
adj[x].pb(y),adj[y].pb(x);
}
dfs(1,0);
bfs();
for(int i=1;i<=m;i++)
{
int l,r;
cin>>l>>r;
if(first[l]>first[r])swap(l,r);
int LCA=lca(l,r);
if(LCA==l)q[i]={i,first[l],first[r]};
else
q[i]={i,last[l],first[r],LCA};
}
len=sqrt(top);
sort(q+1,q+1+m,[](const query &a,const query &b){
int al=get(a.l),bl=get(b.l);
if(al!=bl)return al<bl;
return a.r<b.r;
});
for(int i=0,j=1,res=0,k=1;k<=m;k++)
{
int l=q[k].l,r=q[k].r,id=q[k].id,p=q[k].p;
while(i<r)add(seq[++i],res);
while(i>r)add(seq[i--],res);
while(j<l)add(seq[j++],res);
while(j>l)add(seq[--j],res);
if(p)add(p,res);
ans[id]=res;
if(p)add(p,res);
}
for(int i=1;i<=m;i++)cout<<ans[i]<<'\n';
return 0;
}
__EOF__
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!