洛谷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的正整数。

解决办法

翻译题面

  • 要求动态维护一棵树,对于这棵树:
  1. 查询树上一条链中所有节点中的值的前 \(k\) 大(一个节点可能有多个值)。
  2. 删除某个节点的某个值。
  3. 添加某个值到某个节点。

分析

  • 显然需要进行 树链剖分 ,动态维护树就变成动态维护数组了;
    “查询树上一条链中所有节点中的值的前 \(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;
}

个人觉得难度不是很高,认为可以评蓝吧
写了好久还是不要说这种话了

posted @ 2024-12-27 23:02  IC0CI  阅读(25)  评论(0)    收藏  举报