洛谷P5478 [BJOI2015] 骑士的旅行 题解
题面
[BJOI2015] 骑士的旅行
题目背景
在一片古老的土地上,有一个繁荣的文明有N座城且由恰好N-1条双向道路连接起来,使得任意两座城都是连通的。也就是说,这些城形成了树的结构,任意两座城之间有且仅有一条简单路径。Henry从小就渴望成为一名骑士。勤奋训练许多年后,他决定离开家乡,向那些成名已久的骑士们发起挑战!
题目描述
根据Henry的调查,大陆上一共有 \(M\) 名受封骑士,不妨编号为 \(1\) 到 \(M\)。第 \(i\) 个骑士居住在城 \(P_i\),武力值为 \(F_i\)。
Henry计划进行若干次旅行,每次从某座城出发沿着唯一的简单路径前往另一座城,同时会挑战路线上武力值最高的 \(K\) 个骑士。如果路线上的骑士不足 \(K\) 人,Henry会挑战遇到的 所有人。
每次旅行前,可能会有某些骑士的武力值或定居地发生变化。
输入格式
第一行,一个整数 \(N\) ,表示有 \(N\) 座城,编号为 \(1 \sim N\)。
接下来 \(N-1\) 行,每行两个整数 \(u_i\) 和 \(v_i\),表示城 \(u_i\) 和城 \(v_i\) 之间有一条道路相连。
第 \(N+1\) 行,一个整数 \(M\),表示有 \(M\) 个骑士。
接下来M行,每行两个整数 \(F_i\) 和 \(P_i\) 。按顺序依次表示编号为 \(1 \sim M\) 的每名骑士的武力值和居住地。
第 \(N+M+2\) 行,两个整数 \(Q,~K\),分别表示操作次数和每次旅行挑战的骑士数目上限。
接下来 \(Q\) 行,每行三个整数 \(T_i,~X_i,~Y_i\) 。\(T_i\) 取值范围为 \(\{1,~2,~3\}\),表示操作类型。
一共有以下三种类型的操作:
\(T_i=1\) 时表示一次旅行,Henry将从城 \(X_i\) 出发前往城市 \(Y_i\);
\(T_i=2\) 时表示编号为 \(X_i\) 的骑士的居住地搬到城 \(Y_i\);
\(T_i=3\) 时表示编号为 \(X_i\) 的骑士的武力值修正为 \(Y_i\)。
输出格式
输出若干行,依次为每个旅行的答案。
对每个 \(T_i=1\) 的询问,输出一行,按从大到小的顺序输出Henry在这次旅行中挑战的
所有骑士的武力值。如果路线上没有骑士,输出一行,为一个整数 \(-1\)。
数据范围
100%的数据中,\(1 \leq N,~M \leq 40,000,~1 \leq Ui,~Vi,~Pi \leq N,~1\leq Q \leq 80,000,~1 \leq K \leq 20\),旅行次数不超过 40,000 次,武力值为不超过1,000的正整数。
解决办法
翻译题面
- 要求动态维护一棵树,对于这棵树:
- 查询树上一条链中所有节点中的值的前 \(k\) 大(一个节点可能有多个值)。
- 删除某个节点的某个值。
- 添加某个值到某个节点。
分析
-
显然需要进行 树链剖分 ,动态维护树就变成动态维护数组了;
“查询树上一条链中所有节点中的值的前 \(k\) 大。” 就变成 “查询一段区间中前 \(k\) 大的数。”。 -
单点修改,区间查询。问题就变成维护 线段树 了。
-
发现: “查询一段居间中前 \(k\) 大的数。” 其实和维护 区间最大值 是类似的。
具体地:在 \(push~up\) 的过程中通过归并排序的思想将 \(lson\) 和 \(rson\) 中的前 \(k\) 大存入节点 \(p\) 中。
具体实现
-
树链剖分
-
维护线段树
在维护线段树的时候有一个小细节:叶子结点中的所有元素全部要维护,否则第一个点过不去。
如果暴力插入排序和删除在极端情况下会退化,所以可以使用 \(multiset\) 对原始数据进行维护,每次更新原始数据再存入线段树中。
另外洛谷讨论区:

(坑我都踩了一遍)
代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int N = 40004;
int rd()
{
int x = 0,w = 1;
char ch = 0;
while(ch < '0' || ch > '9')
{
if(ch == '-') w = -1;
ch = getchar();
}
while(ch >= '0' && ch <= '9')
{
x = x * 10 + (ch - '0');
ch = getchar();
}
return x * w;
}
int n,m,q,k,f[N],p[N];
struct node
{
int num[25] = {0};
int cnt = 0;
};
vector<int> adj[N];
int dep[N],siz[N],son[N],pre[N];
void dfs1(int u,int fa)
{
dep[u] = dep[fa] + 1;
siz[u] = 1;
pre[u] = fa;
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 top[N],id[N],tot;
void dfs2(int u,int topc)
{
top[u] = topc;
id[u] = ++tot;
if(!son[u]) return;
dfs2(son[u],topc);
for(auto v : adj[u])
{
if(v == pre[u] || v == son[u]) continue;
dfs2(v,v);
}
}
multiset<int,greater<int> > a[N];
namespace sgt
{
#define ls (p << 1)
#define rs (ls | 1)
#define mid ((pl + pr) >> 1)
node t[N << 2];
node merge(node l,node r)
{
node res;
int i = 0,j = 0;
while(i < l.cnt && j < r.cnt && res.cnt < 20)
{
if(l.num[i + 1] > r.num[j + 1]) res.num[++res.cnt] = l.num[++i];
else res.num[++res.cnt] = r.num[++j];
}
while(i < l.cnt && res.cnt < 20) res.num[++res.cnt] = l.num[++i];
while(j < r.cnt && res.cnt < 20) res.num[++res.cnt] = r.num[++j];
return res;
}
void pu(int p) {t[p] = merge(t[ls],t[rs]);}
void build(int p,int pl,int pr)
{
if(pl == pr)
{
multiset<int,greater<int> >::iterator it = a[pl].begin();
t[p].cnt = 0;
for(int i = 1;i <= 20,it != a[pl].end();i++,it++) t[p].num[i] = *it,t[p].cnt++;
return;
}
build(ls,pl,mid);
build(rs,mid + 1,pr);
pu(p);
}
void upd(int p,int pl,int pr,int lr,int d)
{
if(pl == pr)
{
a[pl].insert(d);
t[p].cnt = 0;
for(auto v : a[pl])
{
t[p].num[++t[p].cnt] = v;
if(t[p].cnt == 20) break;
}
return;
}
if(lr <= mid) upd(ls,pl,mid,lr,d);
if(lr > mid) upd(rs,mid + 1,pr,lr,d);
pu(p);
}
void del(int p,int pl,int pr,int lr,int d)
{
if(pl == pr)
{
a[pl].erase(a[pl].lower_bound(d));
t[p].cnt = 0;
for(auto v : a[pl])
{
t[p].num[++t[p].cnt] = v;
if(t[p].cnt == 20) break;
}
return;
}
if(lr <= mid) del(ls,pl,mid,lr,d);
if(lr > mid) del(rs,mid + 1,pr,lr,d);
pu(p);
}
node qry(int p,int pl,int pr,int l,int r)
{
if(l <= pl && pr <= r)
{
return t[p];
}
node res;
if(l <= mid) res = merge(res,qry(ls,pl,mid,l,r));
if(r > mid) res = merge(res,qry(rs,mid + 1,pr,l,r));
return res;
}
};
void qry(int u,int v)
{
node res;
while(top[u] != top[v])
{
if(dep[top[u]] < dep[top[v]]) swap(u,v);
res = sgt::merge(res,sgt::qry(1,1,n,id[top[u]],id[u]));
u = pre[top[u]];
}
if(dep[u] > dep[v]) swap(u,v);
res = sgt::merge(res,sgt::qry(1,1,n,id[u],id[v]));
if(res.cnt == 0) cout << "-1\n";
else
{
int len = min(res.cnt,k);
for(int i = 1;i <= len;i++) cout << res.num[i] << ' ';
cout << '\n';
}
}
signed main()
{
n = rd();
for(int i = 1;i < n;i++)
{
int u = rd(),v = rd();
adj[u].push_back(v);
adj[v].push_back(u);
}
dfs1(1,0);
dfs2(1,1);
m = rd();
for(int i = 1;i <= m;i++)
{
f[i] = rd(),p[i] = rd();
a[id[p[i]]].insert(f[i]);
}
sgt::build(1,1,n);
q = rd(),k = rd();
while(q--)
{
int opt = rd(),x = rd(),y = rd();
if(opt == 1) qry(x,y);
if(opt == 2)
{
sgt::del(1,1,n,id[p[x]],f[x]);
p[x] = y;
sgt::upd(1,1,n,id[p[x]],f[x]);
}
if(opt == 3)
{
sgt::del(1,1,n,id[p[x]],f[x]);
f[x] = y;
sgt::upd(1,1,n,id[p[x]],f[x]);
}
}
return 0;
}
个人觉得难度不是很高,认为可以评蓝吧
写了好久还是不要说这种话了

浙公网安备 33010602011771号