洛谷 P4114 Qtree1 树链剖分

题面

题目链接

P4114 Qtree1

题目描述

给定一棵 $ n $个节点的树,有两个操作:

CHANGE $ i $ $ t_i $ 把第 $ i $条边的边权变成 $ t_i $

QUERY $ a $ $ b $ 输出从 $ a $ 到 $ b $ 的路径中最大的边权,当 $ a=b $ 的时候,输出 0

输入输出格式

输入格式:

第一行输入一个 $ n $,表示节点个数

第二行到第 $ n $ 行每行输入三个数,$ u_i,v_i,w_i $ ,分别表示 $ u_i,v_i $ 有一条边,边权是 $ w_i $

第 $ n+1 $ 行开始,一共有不定数量行,每一行分别有以下三种可能

CHANGE,QUERY同题意所述

DONE表示输入结束

输出格式:

对于每个QUERY操作,输出一个数,表示 $ a $ $ b $ 之间边权最大值

输入输出样例

输入样例:

3
1 2 1
2 3 2
QUERY 1 2
CHANGE 1 3
QUERY 1 2
DONE

输出样例:

1
3
3

说明

【数据范围】

$1 \leq n \leq 10^5 $

操作次数 $ \leq 3*10^5 $

$ w_i $ 和 $ t_i \leq 2^{31}-1$

说明

【时空限制】

1000ms,512M

思路

熟知的 树链剖分 是解决点权的问题的,而这一题是边权。

由于每个点可以有多个儿子,但只有一个父亲,可以考虑将边权存储到深度较大的点的点权。这样n-1条边的边权就会对应到n-1个点上,而根节点(这里以1为根节点)的点权就是0。

再考虑题中两个操作。

Change

Change操作相对简单。改变两点之间的边权,即改动两点中较深点的点权。

Query

Query操作略有改动。查询两点间路径中所有边的最大边权,如果查询所有经过的点是有问题的。先看图

瞎画的图

要查询3,4两点间所有边的最大边权,如果取点3,2,4点权的最大值,那么答案是5,显然是错误的,因为点2记录的是他与他父亲之间的边权,这是肯定不会经过的边。故在统计答案时,不能统计两点的LCA。联系树剖的基本操作(其中Q为在线段树上查询最大值)

int Ask(int u,int v)
{
    int ans=0;
    while(top[u]!=top[v])
    {
        if(dep[top[u]]<dep[top[v]]) swap(u,v);
        ans=max(ans,Q(1,nid[top[u]],nid[u]));
        u=fa[top[u]];
    }
    if(dep[u]>dep[v]) swap(u,v);
    ans=max(ans,Q(1,nid[u],nid[v]));
    return ans;
}

其中while循环不用改变,while循环结束后,u和v处于同一条链上。

如果u==v,此时u和v都是最初两点的LCA,此时直接返回

否则,u和v之中深度较小的那一点是最初两点的LCA,应该从这个点的重儿子开始统计答案。

故改变后的代码如下

int Ask(int u,int v)
{
    int ans=0;
    while(top[u]!=top[v])
    {
        if(dep[top[u]]<dep[top[v]]) swap(u,v);
        ans=max(ans,Q(1,nid[top[u]],nid[u]));
        u=fa[top[u]];
    }
    if(u==v) return ans;
    if(dep[u]>dep[v]) swap(u,v);
    ans=max(ans,Q(1,nid[u]+1,nid[v]));  
    return ans;
}

AC代码

#include<bits/stdc++.h>
const int maxn=100010;
using namespace std;

int n,wt[maxn];
int tot,to[maxn<<1],nxt[maxn<<1],head[maxn];
int dep[maxn],len[maxn],fa[maxn],son[maxn]; 
int cnt,nid[maxn],nw[maxn],top[maxn];
struct SegmentTree
{
    int l,r,mx;
    #define l(a) tree[a].l
    #define r(a) tree[a].r
    #define m(a) ((l(a)+r(a))>>1)
    #define mx(a) tree[a].mx
}tree[maxn<<2];

void dfs1(int u,int f,int d)
{
    dep[u]=d;fa[u]=f;len[u]=1;
    int maxson=-1;
    for(int i=head[u];i;i=nxt[i])
    {
        int v=to[i];
        if(v==f) continue;
        dfs1(v,u,d+1);
        len[u]+=len[v];
        if(len[v]>maxson) maxson=len[v],son[u]=v;
    }
}

void dfs2(int p,int t)
{
    nid[p]=++cnt;
    top[p]=t;
    if(!son[p]) return;
    dfs2(son[p],t);
    for(int i=head[p];i;i=nxt[i])
    {
        int v=to[i];
        if(v==fa[p] || v==son[p]) continue;
        dfs2(v,v);
    }
}

void BuildTree(int p,int l,int r)
{
    l(p)=l;r(p)=r;
    if(l==r)
    {
        mx(p)=nw[l];
        return;
    }
    BuildTree(p<<1,l(p),m(p));
    BuildTree(p<<1|1,m(p)+1,r);
    mx(p)=max(mx(p<<1),mx(p<<1|1));
}

void Change(int np,int p,int k)
{
    if(l(np)==r(np))
    {
        mx(np)=k;
        return;
    }
    if(p<=m(np)) Change(np<<1,p,k);
    if(p>m(np)) Change(np<<1|1,p,k);
    mx(np)=max(mx(np<<1),mx(np<<1|1));
}

int Q(int p,int l,int r)
{
    if(l<=l(p) && r>=r(p)) return mx(p);
    int ans=0;
    if(l<=m(p)) ans=max(ans,Q(p<<1,l,r));
    if(r>m(p)) ans=max(ans,Q(p<<1|1,l,r));
    return ans;
}

int Ask(int u,int v)
{
    int ans=0;
    while(top[u]!=top[v])
    {
        if(dep[top[u]]<dep[top[v]]) swap(u,v);
        ans=max(ans,Q(1,nid[top[u]],nid[u]));
        u=fa[top[u]];
    }
    if(u==v) return ans;
    if(dep[u]>dep[v]) swap(u,v);
    ans=max(ans,Q(1,nid[u]+1,nid[v]));
    return ans;
}

int main()
{
    scanf("%d",&n);
    for(int i=1;i<n;i++) 
    {
        int u,v,w;scanf("%d%d%d",&u,&v,&w);wt[i]=w;
        to[++tot]=v;nxt[tot]=head[u];head[u]=tot;
        to[++tot]=u;nxt[tot]=head[v];head[v]=tot;
    }
    dfs1(1,1,1);
    dfs2(1,1);
    for(int i=1;i<n;i++)
    {
        int u=to[2*i-1],v=to[2*i];
        if(dep[u]>dep[v]) nw[nid[u]]=wt[i];
        else nw[nid[v]]=wt[i];
    }
    BuildTree(1,1,n);
    while(1)
    {
        string way;cin>>way;if(way=="DONE") break;
        if(way=="CHANGE")
        {
            int i,k;scanf("%d%d",&i,&k);
            int u=to[2*i-1],v=to[2*i];
            if(dep[u]>dep[v]) Change(1,nid[u],k);
            else Change(1,nid[v],k);
        }
        if(way=="QUERY")
        {
            int u,v;scanf("%d%d",&u,&v);
            printf("%d\n",Ask(u,v));
        }
    }
    return 0;
}

总结

树剖处理边权的板子题

转载于:https://www.cnblogs.com/Mercury04/p/9703318.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值