洛谷P2633 Count on a tree 题解
题面
题目描述
给定一棵 \(n\) 个节点的树,每个点有一个权值。有 \(m\) 个询问,每次给你 \(u,v,k\),你需要回答 \(u \text{ xor last}\) 和 \(v\) 这两个节点间第 \(k\) 小的点权。
其中 \(\text{last}\) 是上一个询问的答案,定义其初始为 \(0\),即第一个询问的 \(u\) 是明文。
输入格式
第一行两个整数 \(n,m\)。
第二行有 \(n\) 个整数,其中第 \(i\) 个整数表示点 \(i\) 的权值。
后面 \(n-1\) 行每行两个整数 \(x,y\),表示点 \(x\) 到点 \(y\) 有一条边。
最后 \(m\) 行每行三个整数 \(u,v,k\),表示一组询问。
输出格式
\(m\) 行,每行一个正整数表示每个询问的答案。
数据范围
对于 \(100\%\) 的数据,\(1\le n,m \le 10^5\),点权在 \([1, 2 ^ {31} - 1]\) 之间。
解决办法
第一感觉
-
注意到求区间第 \(k\) 小的点权,立马想到 主席树 维护。
-
再者维护树上一条链的答案,首先想到 树链剖分,但是注意到实际上我们建立的是权值线段树,需要求的仅仅是 \((u,v)\) 的 \(lca\) ,因此我们也可以使用 倍增求 \(lca\) 。
-
注意到数据范围要离散化
具体实现
- 我们遍历这棵树,以每个点到根节点的简单路径建立权值线段树存入主席树中
具体地: 我们在dfs的过程中以父节点为上一版本更新子节点为下一版本
- 使用 树上差分 的思想 替换普通 主席树 中的普通 差分 思想
具体地: \((u,v)\) 的主席树即为 \(t_u + t_v - t_{lca} - t_{pre_{lca}}\) ( \(pre_{lca}\) 即为 \(lca\) 的父节点)
- 使用 树链剖分 或 倍增法 求 \(lca\)
代码
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N = 100005;
int rd(){}//快读
int n,m;
int a[N],olda[N],root[N],len;
vector<int> adj[N];
#define mid ((pl + pr) >> 1)
struct node
{
int ls,rs,sum;
}t[N << 5];
int tot;
int build(int pl,int pr)
{
int rt = ++tot;
t[rt].sum = 0;
if(pl == pr) return rt;
t[rt].ls = build(pl,mid);
t[rt].rs = build(mid + 1,pr);
return rt;
}
int upd(int p,int pl,int pr,int lr)
{
int rt = ++tot;
t[rt] = t[p];
t[rt].sum++;
if(pl == pr) return rt;
if(lr <= mid) t[rt].ls = upd(t[rt].ls,pl,mid,lr);
else t[rt].rs = upd(t[rt].rs,mid + 1,pr,lr);
return rt;
}
int qry(int pl,int pr,int u,int v,int lca,int lcafa,int k)
{
if(pl == pr) return pl;
int x = t[t[u].ls].sum + t[t[v].ls].sum - t[t[lca].ls].sum - t[t[lcafa].ls].sum;
if(x >= k) return qry(pl,mid,t[u].ls,t[v].ls,t[lca].ls,t[lcafa].ls,k);
else return qry(mid + 1,pr,t[u].rs,t[v].rs,t[lca].rs,t[lcafa].rs,k - x);
}
int dep[N],pre[N],siz[N],son[N];
void dfs1(int u,int fa)
{
dep[u] = dep[fa] + 1;
pre[u] = fa;
siz[u] = 1;
root[u] = upd(root[fa],1,len,a[u]);//写在树链剖分里和单独遍历是一样的
for(auto v : adj[u])
{
if(v == fa) continue;
dfs1(v,u);
siz[u] += siz[v];
if(siz[v] > siz[son[u]]) son[u] = v;
}
}
int id[N],w[N],top[N],cnt;
void dfs2(int u,int topc)
{
id[u] = ++cnt;
w[cnt] = a[u];
top[u] = topc;
if(!son[u]) return;
dfs2(son[u],topc);
for(auto v : adj[u])
{
if(v == pre[u] || v == son[u]) continue;
dfs2(v,v);
}
}
int lca(int u,int v)
{
while(top[u] != top[v])
{
if(dep[top[u]] < dep[top[v]]) swap(u,v);
u = pre[top[u]];
}
if(dep[u] > dep[v]) swap(u,v);
return u;
}
signed main()
{
n = rd(),m = rd();
for(int i = 1;i <= n;i++) a[i] = olda[i] = rd();
for(int i = 1;i < n;i++)
{
int u = rd(),v = rd();
adj[u].push_back(v);
adj[v].push_back(u);
}
sort(olda + 1,olda + n + 1);//离散化
len = unique(olda + 1,olda + n + 1) - olda - 1;
for(int i = 1;i <= n;i++) a[i] = lower_bound(olda + 1,olda + len + 1,a[i]) - olda;
root[0] = build(1,len);//建空树
dfs1(1,0);
dfs2(1,1);
int ans = 0;
while(m--)
{
int u = rd(),v = rd(),k = rd();
u = u ^ ans;//根据题意操作
int ca = lca(u,v);
ans = olda[qry(1,len,root[u],root[v],root[ca],root[pre[ca]],k)];//离散化还原
cout << ans << '\n';
}
return 0;
}
这个题是一个缝合怪,不好玩,但是练手还是不错的。

浙公网安备 33010602011771号