返回顶部

P1505 [国家集训队]旅游

人生第二次单切紫题,值得纪念!!!

原题传送锚点

[国家集训队]旅游

题目背景

Ray 乐忠于旅游,这次他来到了 T 城。T 城是一个水上城市,一共有 n 个景点,有些景点之间会用一座桥连接。为了方便游客到达每个景点但又为了节约成本,T 城的任意两个景点之间有且只有一条路径。换句话说, T 城中只有 n−1 座桥。

Ray 发现,有些桥上可以看到美丽的景色,让人心情愉悦,但有些桥狭窄泥泞,令人烦躁。于是,他给每座桥定义一个愉悦度 w,也就是说,Ray 经过这座桥会增加 w 的愉悦度,这或许是正的也可能是负的。有时,Ray 看待同一座桥的心情也会发生改变。

现在,Ray 想让你帮他计算从 u 景点到 v 景点能获得的总愉悦度。有时,他还想知道某段路上最美丽的桥所提供的最大愉悦度,或是某段路上最糟糕的一座桥提供的最低愉悦度。

题目描述

给定一棵 n 个节点的树,边带权,编号 0∼n−1,需要支持五种操作:

  • C i w 将输入的第 i 条边权值改为 w
  • N u v 将 u,v 节点之间的边权都变为相反数
  • SUM u v 询问 u,v 节点之间边权和
  • MAX u v 询问 u,v 节点之间边权最大值
  • MIN u v 询问 u,v 节点之间边权最小值

保证任意时刻所有边的权值都在 [−1000,1000]内。

输入格式

第一行一个正整数 n,表示节点个数。
接下来 
n−1 行,每行三个整数 u,v,w,表示u,v之间有一条权值为w 的边,描述这棵树。
然后一行一个正整数 
m,表示操作数。
接下来 m行,每行表示一个操作。

输出格式

对于每一个询问操作,输出一行一个整数表示答案。

样例 #1

样例输入 #1

3
0 1 1
1 2 2
8
SUM 0 2
MAX 0 2
N 0 1
SUM 0 2
MIN 0 2
C 1 3
SUM 0 2
MAX 0 2

样例输出 #1

3
2
1
-1
5
3

提示

【数据范围】

对于100%的数据,1≤n,m≤2×105

浅读一下题,发现有点类似树剖,不过这道题是修改和查询边权。

考虑边权转点权,挑选一个根节点,dfs时将边权下放到深度更大的点上。

然后就是基本的树剖操作了:

复制代码
//........................................树剖基操 
void dfs1(int x,int y)
{
    d[x]=d[y]+1;
    si[x]=1;
    for(int i=fi[x];i;i=ne[i])
    {
        int v=to[i];
        if(v==y) continue;
        a[v]=w[i],b[(i+1)/2]=v;//特殊处理:b[i]==v表示第i条边的信息处理在结点v上 
        //由于又是双向存边,所以要用当前边编号加一后除二向下取整 
        //v为边连接两个结点中深度较大的结点 
        dfs1(v,x);
        fa[v]=x;
        si[x]+=si[v];
        if(si[x]>si[son[x]]) son[x]=v;
    }
}
 
void dfs2(int x,int t)
{
    top[x]=t;
    id[x]=++num;
    val[num]=a[x];
    if(son[x]) dfs2(son[x],t);
    for(int i=fi[x];i;i=ne[i])
   
        int v=to[i];
        if(v==fa[x]||v==son[x]) continue;
        dfs2(v,v);
    }
}
//...........................................over
复制代码

 

接下来就是线段树了。

首先介绍懒标记:懒标记维护的是当前结点是否取反,即取相反数。

区间和取相反数加负号就行,最大值和最小值先取相反数,然后互相交换值即可。

复制代码
void pu(int x)//懒标记下传 
{
    if(la[x])
    {
        tr[x<<1].w=-tr[x<<1].w,tr[x<<1].a=-tr[x<<1].a,tr[x<<1].i=-tr[x<<1].i;
        tr[x<<1|1].w=-tr[x<<1|1].w,tr[x<<1|1].a=-tr[x<<1|1].a,tr[x<<1|1].i=-tr[x<<1|1].i;
        //左右子结点均取反 
        swap(tr[x<<1].a,tr[x<<1].i);
        swap(tr[x<<1|1].a,tr[x<<1|1].i);
        //由于取反,故而最大值最小值交换 
        la[x<<1]^=la[x],la[x<<1|1]^=la[x];
        la[x]=0;
        //处理懒标记 
    }
}
复制代码

然后基操建树。

对应操作修改和查询。

需要注意的是:由于我们是将边权下放到边连接的两结点中深度较大的结点,故而更新和查询的时候处理的结点不包括边界两边对应两结点的lca。

复制代码
//.........................................线段树 
void bu(int x,int l,int r)//建树 
{
    tr[x].l=l,tr[x].r=r;
    if(l==r)
    {
        tr[x].w=tr[x].a=tr[x].i=val[l];
        return;
    }
    int mid=(l+r)>>1;
    bu(x<<1,l,mid),bu(x<<1|1,mid+1,r);
    tr[x].w=tr[x<<1].w+tr[x<<1|1].w;
    tr[x].a=max(tr[x<<1].a,tr[x<<1|1].a);
    tr[x].i=min(tr[x<<1].i,tr[x<<1|1].i);
}

void u1(int x,int s,int k)//修改对应边权,处理在边权对应的结点上 
{
    pu(x);
    if(tr[x].l==tr[x].r)
    {
        tr[x].w=tr[x].a=tr[x].i=k;
        return;
    }
    int mid=(tr[x].l+tr[x].r)>>1;
    if(s<=mid) u1(x<<1,s,k);
    else u1(x<<1|1,s,k);
    tr[x].w=tr[x<<1].w+tr[x<<1|1].w;
    tr[x].a=max(tr[x<<1].a,tr[x<<1|1].a);
    tr[x].i=min(tr[x<<1].i,tr[x<<1|1].i);
}

void u2(int x,int l,int r)//修改区间,全部取相反数 
{
    pu(x);
    if(l<=tr[x].l&&tr[x].r<=r)
    {
        tr[x].w=-tr[x].w,tr[x].a=-tr[x].a,tr[x].i=-tr[x].i;
        swap(tr[x].a,tr[x].i);
        la[x]^=1;
        return;
    }
    int mid=(tr[x].l+tr[x].r)>>1;
    if(l<=mid) u2(x<<1,l,r);
    if(r>mid) u2(x<<1|1,l,r);
    tr[x].w=tr[x<<1].w+tr[x<<1|1].w;
    tr[x].a=max(tr[x<<1].a,tr[x<<1|1].a);
    tr[x].i=min(tr[x<<1].i,tr[x<<1|1].i);
}

void up(int x,int y)//修改树上路径,由于边权对应的是深度大的结点,所以修改不包括两结点的lca 
{
    while(top[x]^top[y])
    {
        if(d[top[x]<d[top[y]]]) swap(x,y);
        u2(1,id[top[x]],id[x]);
        x=fa[top[x]];
    }
    if(d[x]>d[y]) swap(x,y);
    u2(1,id[x]+1,id[y]);//id[x]+1:不修改lca 
}

int q1(int x,int l,int r)//区间和 
{
    pu(x);
    if(l<=tr[x].l&&tr[x].r<=r)
        return tr[x].w;
    int mid=(tr[x].l+tr[x].r)>>1;
    int s=0;
    if(l<=mid) s+=q1(x<<1,l,r);
    if(r>mid) s+=q1(x<<1|1,l,r);
    return s;
}

int q2(int x,int l,int r)//区间最大 
{
    pu(x);
    if(l<=tr[x].l&&tr[x].r<=r)
        return tr[x].a;
    int mid=(tr[x].l+tr[x].r)>>1;
    int s=-INT_MAX;
    if(l<=mid) s=max(s,q2(x<<1,l,r));
    if(r>mid) s=max(s,q2(x<<1|1,l,r));
    return s;
}

int q3(int x,int l,int r)//区间最小 
{
    pu(x);
    if(l<=tr[x].l&&tr[x].r<=r)
        return tr[x].i;
    int mid=(tr[x].l+tr[x].r)>>1;
    int s=INT_MAX;
    if(l<=mid) s=min(s,q3(x<<1,l,r));
    if(r>mid) s=min(s,q3(x<<1|1,l,r));
    return s;
}
//查询和修改一个道理,不包括lca 
int qu1(int x,int y)//边权和 
{
    int s=0;
    while(top[x]^top[y])
    {
        if(d[top[x]]<d[top[y]]) swap(x,y);
        s+=q1(1,id[top[x]],id[x]);
        x=fa[top[x]];
    }
    if(d[x]>d[y]) swap(x,y);
    s+=q1(1,id[x]+1,id[y]);
    return s;
}

int qu2(int x,int y)//边权最大值 
{
    int s=-INT_MAX;
    while(top[x]^top[y])
    {
        if(d[top[x]]<d[top[y]]) swap(x,y);
        s=max(s,q2(1,id[top[x]],id[x]));
        x=fa[top[x]];
    }
    if(d[x]>d[y]) swap(x,y);
    s=max(s,q2(1,id[x]+1,id[y]));
    return s;
}

int qu3(int x,int y)//边权最小值 
{
    int s=INT_MAX;
    while(top[x]^top[y])
    {
        if(d[top[x]]<d[top[y]]) swap(x,y);
        s=min(s,q3(1,id[top[x]],id[x]));
        x=fa[top[x]];
    }
    if(d[x]>d[y]) swap(x,y);
    s=min(s,q3(1,id[x]+1,id[y]));
    return s;
}
//..........................................over
复制代码

Code:

复制代码
#include<bits/stdc++.h>
using namespace std;
const int N=4e5+5;
int n,m,tot,num;
int fa[N],d[N],si[N],son[N],a[N];
int id[N],val[N],top[N],la[N],b[N];
int fi[N],ne[N*2],to[N*2],w[N*2];
struct xiao
{
    int l,r,w,a,i;//w表示区间和,a表示区间最大,i表示区间最小 
}tr[N*4];

void add(int x,int y,int z)//手写邻接表 
{
    ne[++tot]=fi[x];
    fi[x]=tot;
    to[tot]=y;
    w[tot]=z;
}

void pu(int x)//懒标记下传 
{
    if(la[x])
    {
        tr[x<<1].w=-tr[x<<1].w,tr[x<<1].a=-tr[x<<1].a,tr[x<<1].i=-tr[x<<1].i;
        tr[x<<1|1].w=-tr[x<<1|1].w,tr[x<<1|1].a=-tr[x<<1|1].a,tr[x<<1|1].i=-tr[x<<1|1].i;
        //左右子结点均取反 
        swap(tr[x<<1].a,tr[x<<1].i);
        swap(tr[x<<1|1].a,tr[x<<1|1].i);
        //由于取反,故而最大值最小值交换 
        la[x<<1]^=la[x],la[x<<1|1]^=la[x];
        la[x]=0;
        //处理懒标记 
    }
}
//........................................树剖基操 
void dfs1(int x,int y)
{
    d[x]=d[y]+1;
    si[x]=1;
    for(int i=fi[x];i;i=ne[i])
    {
        int v=to[i];
        if(v==y) continue;
        a[v]=w[i],b[(i+1)/2]=v;//特殊处理:b[i]==v表示第i条边的信息处理在结点v上 
        //由于又是双向存边,所以要用当前边编号加一后除二向下取整 
        //v为边连接两个结点中深度较大的结点 
        dfs1(v,x);
        fa[v]=x;
        si[x]+=si[v];
        if(si[x]>si[son[x]]) son[x]=v;
    }
}
 
void dfs2(int x,int t)
{
    top[x]=t;
    id[x]=++num;
    val[num]=a[x];
    if(son[x]) dfs2(son[x],t);
    for(int i=fi[x];i;i=ne[i])
    {
        int v=to[i];
        if(v==fa[x]||v==son[x]) continue;
        dfs2(v,v);
    }
}
//...........................................over
//.........................................线段树 
void bu(int x,int l,int r)//建树 
{
    tr[x].l=l,tr[x].r=r;
    if(l==r)
    {
        tr[x].w=tr[x].a=tr[x].i=val[l];
        return;
    }
    int mid=(l+r)>>1;
    bu(x<<1,l,mid),bu(x<<1|1,mid+1,r);
    tr[x].w=tr[x<<1].w+tr[x<<1|1].w;
    tr[x].a=max(tr[x<<1].a,tr[x<<1|1].a);
    tr[x].i=min(tr[x<<1].i,tr[x<<1|1].i);
}

void u1(int x,int s,int k)//修改对应边权,处理在边权对应的结点上 
{
    pu(x);
    if(tr[x].l==tr[x].r)
    {
        tr[x].w=tr[x].a=tr[x].i=k;
        return;
    }
    int mid=(tr[x].l+tr[x].r)>>1;
    if(s<=mid) u1(x<<1,s,k);
    else u1(x<<1|1,s,k);
    tr[x].w=tr[x<<1].w+tr[x<<1|1].w;
    tr[x].a=max(tr[x<<1].a,tr[x<<1|1].a);
    tr[x].i=min(tr[x<<1].i,tr[x<<1|1].i);
}

void u2(int x,int l,int r)//修改区间,全部取相反数 
{
    pu(x);
    if(l<=tr[x].l&&tr[x].r<=r)
    {
        tr[x].w=-tr[x].w,tr[x].a=-tr[x].a,tr[x].i=-tr[x].i;
        swap(tr[x].a,tr[x].i);
        la[x]^=1;
        return;
    }
    int mid=(tr[x].l+tr[x].r)>>1;
    if(l<=mid) u2(x<<1,l,r);
    if(r>mid) u2(x<<1|1,l,r);
    tr[x].w=tr[x<<1].w+tr[x<<1|1].w;
    tr[x].a=max(tr[x<<1].a,tr[x<<1|1].a);
    tr[x].i=min(tr[x<<1].i,tr[x<<1|1].i);
}

void up(int x,int y)//修改树上路径,由于边权对应的是深度大的结点,所以修改不包括两结点的lca 
{
    while(top[x]^top[y])
    {
        if(d[top[x]<d[top[y]]]) swap(x,y);
        u2(1,id[top[x]],id[x]);
        x=fa[top[x]];
    }
    if(d[x]>d[y]) swap(x,y);
    u2(1,id[x]+1,id[y]);//id[x]+1:不修改lca 
}

int q1(int x,int l,int r)//区间和 
{
    pu(x);
    if(l<=tr[x].l&&tr[x].r<=r)
        return tr[x].w;
    int mid=(tr[x].l+tr[x].r)>>1;
    int s=0;
    if(l<=mid) s+=q1(x<<1,l,r);
    if(r>mid) s+=q1(x<<1|1,l,r);
    return s;
}

int q2(int x,int l,int r)//区间最大 
{
    pu(x);
    if(l<=tr[x].l&&tr[x].r<=r)
        return tr[x].a;
    int mid=(tr[x].l+tr[x].r)>>1;
    int s=-INT_MAX;
    if(l<=mid) s=max(s,q2(x<<1,l,r));
    if(r>mid) s=max(s,q2(x<<1|1,l,r));
    return s;
}

int q3(int x,int l,int r)//区间最小 
{
    pu(x);
    if(l<=tr[x].l&&tr[x].r<=r)
        return tr[x].i;
    int mid=(tr[x].l+tr[x].r)>>1;
    int s=INT_MAX;
    if(l<=mid) s=min(s,q3(x<<1,l,r));
    if(r>mid) s=min(s,q3(x<<1|1,l,r));
    return s;
}
//查询和修改一个道理,不包括lca 
int qu1(int x,int y)//边权和 
{
    int s=0;
    while(top[x]^top[y])
    {
        if(d[top[x]]<d[top[y]]) swap(x,y);
        s+=q1(1,id[top[x]],id[x]);
        x=fa[top[x]];
    }
    if(d[x]>d[y]) swap(x,y);
    s+=q1(1,id[x]+1,id[y]);
    return s;
}

int qu2(int x,int y)//边权最大值 
{
    int s=-INT_MAX;
    while(top[x]^top[y])
    {
        if(d[top[x]]<d[top[y]]) swap(x,y);
        s=max(s,q2(1,id[top[x]],id[x]));
        x=fa[top[x]];
    }
    if(d[x]>d[y]) swap(x,y);
    s=max(s,q2(1,id[x]+1,id[y]));
    return s;
}

int qu3(int x,int y)//边权最小值 
{
    int s=INT_MAX;
    while(top[x]^top[y])
    {
        if(d[top[x]]<d[top[y]]) swap(x,y);
        s=min(s,q3(1,id[top[x]],id[x]));
        x=fa[top[x]];
    }
    if(d[x]>d[y]) swap(x,y);
    s=min(s,q3(1,id[x]+1,id[y]));
    return s;
}
//..........................................over 
int main()
{
    cin>>n;
    for(int i=1;i<n;i++)
    {
        int x,y,z;
        cin>>x>>y>>z;
        add(x,y,z),add(y,x,z);
    }
    dfs1(0,-1);
    dfs2(0,0);
    bu(1,1,n);
    cin>>m;
    while(m--)
    {
        char c[3];
        int x,y;
        cin>>c;
        cin>>x>>y;
        if(c[0]=='C')
            u1(1,id[b[x]],y);
        if(c[0]=='N')
            up(x,y);
        if(c[0]=='S')
            cout<<qu1(x,y)<<'\n';
        if(c[0]=='M')
        {
            if(c[1]=='A')
                cout<<qu2(x,y)<<'\n';
            else 
                cout<<qu3(x,y)<<'\n';
        }
    }
}
复制代码


如果您觉得阅读本文对您有帮助,请点一下“推荐”按钮,您的“推荐”将是我最大的写作动力!欢迎各位转载,但是未经作者本人同意,转载文章之后必须在文章页面明显位置给出作者和原文连接,否则保留追究法律责任的权利。
posted @   光暗之影x  阅读(13)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示