洛谷 P2590 树的统计 P3178 树上操作【树链剖分入门】

题目描述

一棵树上有n个节点,编号分别为1到n,每个节点都有一个权值w。

我们将以下面的形式来要求你对这棵树完成一些操作:

I. CHANGE u t : 把结点u的权值改为t

II. QMAX u v: 询问从点u到点v的路径上的节点的最大权值

III. QSUM u v: 询问从点u到点v的路径上的节点的权值和

注意:从点u到点v的路径上的节点包括u和v本身

输入格式:

输入文件的第一行为一个整数n,表示节点的个数。

接下来n – 1行,每行2个整数a和b,表示节点a和节点b之间有一条边相连。

接下来一行n个整数,第i个整数wi表示节点i的权值。

接下来1行,为一个整数q,表示操作的总数。

接下来q行,每行一个操作,以“CHANGE u t”或者“QMAX u v”或者“QSUM u v”的形式给出。

输出格式:

对于每个“QMAX”或者“QSUM”的操作,每行输出一个整数表示要求输出的结果。

输入样例

4

1 2

2 3

4 1

4 2 1 3

12

QMAX 3 4

QMAX 3 3

QMAX 3 2

QMAX 2 3

QSUM 3 4

QSUM 2 1

CHANGE 1 5

QMAX 3 4

CHANGE 3 6

QMAX 3 4

QMAX 2 4

QSUM 3 4

输出样例

4

1

2

2

10

6

5

6

5

16

说明

对于100%的数据,保证1<=n<=30000,0<=q<=200000;中途操作中保证每个节点的权值w在-30000到30000之间。



题目分析:

树链剖分入门

首先树链剖分两个dfs与处理好

然后就直接用预处理出的编号建线段树

对于CHANGE操作

直接在线段树上对num[u]进行单点修改

对于QSUM操作

我们先比较top[u]和top[v]是否相同(即两者是否在同一条重链上)

若不在,我们设dep[u]较大

我们先对ll=num[ top[u] ]到rr=num[u]这段查询区间和,并以ans记录

然后另u=fa[ top[u] ],重复上述操作

若top[u]和top[v]相同

设dep[u] < dep[v]

再次ans+=ll=num[u]到rr=num[u]的区间和

然后返回ans

QMAX操作也是类似

具体解释请看代码注释


#include<iostream>
#include<cstdio>
#include<vector>
#include<queue>
#include<algorithm>
#include<cstring>
using namespace std;

int read()
{
    int f=1,x=0;
    char ss=getchar();
    while(ss<‘0‘||ss>‘9‘){if(ss==‘-‘)f=-1;ss=getchar();}
    while(ss>=‘0‘&&ss<=‘9‘){x=x*10+ss-‘0‘;ss=getchar();}
    return f*x;
}

void print(int x)
{
    if(x<0){putchar(‘-‘);x=-x;}
    if(x>9)print(x/10);
    putchar(x%10+‘0‘);
}

int n,t;
int tot;
struct node{int v,nxt;}E[100010];
int head[100010];
int w[100010];
int cnt;
int dep[100010],fa[100010];
int size[100010],son[100010];
int top[100010],num[100010],pre[100010];
int sum[400010],maxn[400010];
char ss[20];

void add(int u,int v)
{
    E[++tot].v=v;
    E[tot].nxt=head[u];
    head[u]=tot;
}

void dfs1(int u,int pa)
{
    size[u]=1;
    for(int i=head[u];i;i=E[i].nxt)
    {
        int v=E[i].v;
        if(v==pa) continue;
        dep[v]=dep[u]+1;  fa[v]=u;
        dfs1(v,u);
        size[u]+=size[v];
        if(size[v]>size[son[u]]) son[u]=v;
    }
}

void dfs2(int u,int tp)
{
    num[u]=++cnt; pre[cnt]=u; top[u]=tp;
    if(son[u]) dfs2(son[u],tp);
    for(int i=head[u];i;i=E[i].nxt)
    {
        int v=E[i].v;
        if(v==fa[u]||v==son[u]) continue;
        dfs2(v,v);
    }
}

void push(int p)
{
    maxn[p]=max(maxn[p<<1],maxn[p<<1|1]);
    sum[p]=sum[p<<1]+sum[p<<1|1];
}

void build(int s,int t,int p)
{
    if(s==t){ maxn[p]=sum[p]=w[ pre[s] ]; return; }
    int mid=(s+t)>>1;
    build(s,mid,p<<1); build(mid+1,t,p<<1|1);
    push(p);
}

void update(int u,int w,int s,int t,int p)
{
    if(s==t){maxn[p]=sum[p]=w;return;}
    int mid=(s+t)>>1;
    if(u<=mid) update(u,w,s,mid,p<<1);
    else update(u,w,mid+1,t,p<<1|1);
    push(p);
}

int getmax(int ll,int rr,int s,int t,int p)
{
    if(ll<=s&&t<=rr) return maxn[p];
    int mid=(s+t)>>1;
    int ans=-1e9;
    if(ll<=mid) ans=max(ans, getmax(ll,rr,s,mid,p<<1) );
    if(rr>mid) ans=max(ans, getmax(ll,rr,mid+1,t,p<<1|1) );
    return ans;
}

int qmax(int u,int v)
{
    int ans=-1e9;
    while (top[u]!=top[v])
    {
        if (dep[top[u]]<dep[top[v]])swap(u,v);
        ans=max( ans,getmax(num[top[u]],num[u],1,n,1) );
        u=fa[top[u]];
    }
    if (dep[u]<dep[v])swap(u,v);
    ans=max(ans,getmax(num[v],num[u],1,n,1));
    return ans;
}

int getsum(int ll,int rr,int s,int t,int p)
{
    if(ll<=s&&t<=rr) return sum[p];
    int mid=(s+t)>>1;
    int ans=0;
    if(ll<=mid) ans+=getsum(ll,rr,s,mid,p<<1) ;
    if(rr>mid) ans+=getsum(ll,rr,mid+1,t,p<<1|1) ;
    return ans;
}

int qsum(int u,int v)
{
    int ans=0;
    while(top[u]!=top[v])//若u和v不在一条重链上
    {
        if( dep[ top[u] ] < dep[ top[v] ]) swap(u,v);//取top深度较大的一方
        ans+=getsum(num[top[u]],num[u],1,n,1);//更新u到其top
        u=fa[top[u]];//另u跳到其top的父亲
    }
    if(dep[u]<dep[v]) swap(u,v);//在一条重链上,直接区间更新
    ans+=getsum(num[v],num[u],1,n,1);
    return ans;
}

int main()
{
    n=read();
    for(int i=1;i<n;i++)
    {
        int u=read(),v=read();
        add(u,v);add(v,u);
    }
    for(int i=1;i<=n;i++)
    w[i]=read();

    dep[1]=1; fa[1]=1;
    dfs1(1,-1); dfs2(1,1);
    build(1,n,1);//预处理——树剖

    t=read();
    while(t--)
    {
        scanf("%s",&ss);
        int x=read(),y=read();
        if(ss[1]==‘H‘) update(num[x],y,1,n,1);//直接更新num[x]
        else if(ss[1]==‘M‘) print(qmax(x,y)),printf("\n");
        else if(ss[1]==‘S‘) print(qsum(x,y)),printf("\n");
    }
    return 0;
}

题目描述

有一棵点数为 N 的树,以点 1 为根,且树点有边权。然后有 M 个操作,分为三种:操作 1 :把某个节点 x 的点权增加 a 。操作 2 :把某个节点 x 为根的子树中所有点的点权都增加 a 。操作 3 :询问某个节点 x 到根的路径中所有点的点权和。

输入格式:

第一行包含两个整数 N, M 。表示点数和操作数。接下来一行 N 个整数,表示树中节点的初始权值。接下来 N-1 行每行两个正整数 from, to , 表示该树中存在一条边 (from, to) 。再接下来 M 行,每行分别表示一次操作。其中第一个数表示该操作的种类( 1-3 ) ,之后接这个操作的参数( x 或者 x a ) 。

输出格式:

对于每个询问操作,输出该询问的答案。答案之间用换行隔开。

输入样例

5 5

1 2 3 4 5

1 2

1 4

2 3

2 5

3 3

1 2 1

3 5

2 1 2

3 3

输出样例

6

9

13

说明

对于 100% 的数据, N,M<=100000 ,且所有输入数据的绝对值都不

会超过 10^6 。


题目分析:

这题与上面唯一不同的是要更新u的所有子树

不难发现其实u及其所有子节点

在线段树上的编号

就是ll=num[u]到rr=num[u]+size[u]-1的连续区间

知道这点其实就不难做了


#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
#include<vector>
using namespace std;
typedef long long lt;

lt read()
{
    lt f=1,x=0;
    char ss=getchar();
    while(ss<‘0‘||ss>‘9‘){if(ss==‘-‘)f=-1;ss=getchar();}
    while(ss>=‘0‘&&ss<=‘9‘){x=x*10+ss-‘0‘;ss=getchar();}
    return x*f;
}

void print(lt x)
{
    if(x<0){putchar(‘-‘);x=-x;}
    if(x>9)print(x/10);
    putchar(x%10+‘0‘);
}

lt n,m;
lt d[100010];
struct node{lt v,nxt;}E[200010];
lt head[100010];
lt cnt,tot;
lt dep[100010],fa[100010],son[100010];
lt top[100010],size[100010];
lt num[100010],pos[100010];
lt sum[1000010],add[1000010];

void adde(lt u,lt v)
{
    E[++tot].nxt=head[u];
    E[tot].v=v;
    head[u]=tot;
}

void dfs1(lt u,lt pa)
{
    size[u]=1;
    for(lt i=head[u];i;i=E[i].nxt)
    {
        lt v=E[i].v;
        if(v==pa) continue;
        dep[v]=dep[u]+1;  fa[v]=u;
        dfs1(v,u);
        size[u]+=size[v];
        if(size[v]>size[son[u]]) son[u]=v;
    }
}

void dfs2(lt u,lt tp)
{
    num[u]=++cnt; pos[cnt]=u; top[u]=tp;
    if(son[u]) dfs2(son[u],tp);
    for(lt i=head[u];i;i=E[i].nxt)
    {
        lt v=E[i].v;
        if(v==fa[u]||v==son[u]) continue;
        dfs2(v,v);
    }
}

void push(lt mid,lt s,lt t,lt p)
{
    add[p<<1]+=add[p]; add[p<<1|1]+=add[p];
    sum[p<<1]+=add[p]*(mid-s+1);
    sum[p<<1|1]+=add[p]*(t-mid);
    add[p]=0;
}

void build(lt s,lt t,lt p)
{
    if(s==t){ sum[p]=d[ pos[s] ]; return; }
    lt mid=(s+t)>>1;
    build(s,mid,p<<1); build(mid+1,t,p<<1|1);
    sum[p]=sum[p<<1]+sum[p<<1|1];
}

void update(lt u,lt w,lt s,lt t,lt p)
{
    if(s==t){sum[p]+=w;return;}
    lt mid=(s+t)>>1;
    if(add[p])push(mid,s,t,p);
    if(u<=mid) update(u,w,s,mid,p<<1);
    else update(u,w,mid+1,t,p<<1|1);
    sum[p]=sum[p<<1]+sum[p<<1|1];
}

void uprange(lt ll,lt rr,lt s,lt t,lt p,lt w)
{
    if(ll<=s&&t<=rr){sum[p]+=(t-s+1)*w;add[p]+=w;return;}
    lt mid=(s+t)>>1;
    if(add[p])push(mid,s,t,p);
    if(ll<=mid)uprange(ll,rr,s,mid,p<<1,w);
    if(rr>mid)uprange(ll,rr,mid+1,t,p<<1|1,w);
    sum[p]=sum[p<<1]+sum[p<<1|1];
}

lt getsum(lt ll,lt rr,lt s,lt t,lt p)
{
    if(ll<=s&&t<=rr) return sum[p];
    lt mid=(s+t)>>1;
    if(add[p])push(mid,s,t,p);
    lt ans=0;
    if(ll<=mid) ans+=getsum(ll,rr,s,mid,p<<1) ;
    if(rr>mid) ans+=getsum(ll,rr,mid+1,t,p<<1|1) ;
    return ans;
}

lt qsum(lt u,lt v)
{
    lt ans=0;
    while(top[u]!=top[v])
    {
        if( dep[ top[u] ] < dep[ top[v] ]) swap(u,v);
        ans+=getsum(num[top[u]],num[u],1,n,1);
        u=fa[top[u]];
    }
    if(dep[u]>dep[v]) swap(u,v);
    ans+=getsum(num[u],num[v],1,n,1);
    return ans;
}

int main()
{
    n=read();m=read();
    for(lt i=1;i<=n;i++)
    d[i]=read();

    for(lt i=1;i<n;i++)
    {
        lt u=read(),v=read();
        adde(u,v);adde(v,u);
    }

    dep[1]=1; fa[1]=1;
    dfs1(1,-1); dfs2(1,1);
    build(1,n,1);

    while(m--)
    {
        lt k=read(),u=read();
        if(k==1){ lt x=read(); update(num[u],x,1,n,1); }
        else if(k==2) { lt x=read(); uprange(num[u],num[u]+size[u]-1,1,n,1,x); }
        else if(k==3) { print(qsum(u,1)); printf("\n"); }
    }
    return 0;
}

原文地址:https://www.cnblogs.com/niiick/p/8527528.html

时间: 2024-10-03 22:42:24

洛谷 P2590 树的统计 P3178 树上操作【树链剖分入门】的相关文章

树链剖分入门-Hdu3966 Aragorn&#39;s Story

AC通道:http://acm.hdu.edu.cn/showproblem.php?pid=3966 [题目大意] 一棵树上每个点有权值,每次支持三种操作:给[a,b]路径上的所有节点的权值加上k,给[a,b]路径上的所有节点的权值减去k,以及询问a的权值. [分析] 这是一道树链剖分模板题. 树链剖分,就是将树化成了许多链,将这些链用数据结构保存起来,再去维护这个数据结构. 假设给的树就是一条链,这道题当然很好办:直接将链用线段树存了,因为[a,b]的路径在线段树上也是连续的一段,那么修改一

HDU - 3966 Aragorn&#39;s Story(树链剖分入门+线段树)

HDU - 3966 Aragorn's Story Time Limit: 3000MS   Memory Limit: 32768KB   64bit IO Format: %I64d & %I64u Submit Status Description Our protagonist is the handsome human prince Aragorn comes from The Lord of the Rings. One day Aragorn finds a lot of ene

洛谷P2982 [USACO10FEB]慢下来Slowing down(线段树 DFS序 区间增减 单点查询)

To 洛谷.2982 慢下来Slowing down 题目描述 Every day each of Farmer John's N (1 <= N <= 100,000) cows conveniently numbered 1..N move from the barn to her private pasture. The pastures are organized as a tree, with the barn being on pasture 1. Exactly N-1 cow

洛谷P2879 [USACO07JAN]区间统计Tallest Cow

To 洛谷.2879 区间统计 题目描述 FJ's N (1 ≤ N ≤ 10,000) cows conveniently indexed 1..N are standing in a line. Each cow has a positive integer height (which is a bit of secret). You are told only the height H (1 ≤ H ≤ 1,000,000) of the tallest cow along with th

【刷题】洛谷 P3834 【模板】可持久化线段树 1(主席树)

题目背景 这是个非常经典的主席树入门题--静态区间第K小 数据已经过加强,请使用主席树.同时请注意常数优化 题目描述 如题,给定N个正整数构成的序列,将对于指定的闭区间查询其区间内的第K小值. 输入输出格式 输入格式: 第一行包含两个正整数N.M,分别表示序列的长度和查询的个数. 第二行包含N个正整数,表示这个序列各项的数字. 接下来M行每行包含三个整数l, r, kl,r,k , 表示查询区间[l, r][l,r] 内的第k小值. 输出格式: 输出包含k行,每行1个正整数,依次表示每一次查询的

[BZOJ1036][ZJOI2008]树的统计Count 解题报告|树链剖分

树链剖分 简单来说就是数据结构在树上的应用.常用的为线段树splay等.(可现在splay还不会敲囧) 重链剖分: 将树上的边分成轻链和重链. 重边为每个节点到它子树最大的儿子的边,其余为轻边. 设(u,v)为轻边,则size(v)<=size(u)/2 (一旦大于了那必然是重边) 也就是一条路径上每增加一条轻边节点个数就会减少一半以上,那么显然根到任意一个节点路径上的轻边条数一定不会超过log(n)(不然节点就没了啊23333) 重链定义为一条极长的连续的且全由重边构成的链. 容易看出重链两两

【BZOJ4034】[HAOI2015]树上操作 树链剖分+线段树

[BZOJ4034][HAOI2015]树上操作 Description 有一棵点数为 N 的树,以点 1 为根,且树点有边权.然后有 M 个 操作,分为三种: 操作 1 :把某个节点 x 的点权增加 a . 操作 2 :把某个节点 x 为根的子树中所有点的点权都增加 a . 操作 3 :询问某个节点 x 到根的路径中所有点的点权和. Input 第一行包含两个整数 N, M .表示点数和操作数. 接下来一行 N 个整数,表示树中节点的初始权值. 接下来 N-1 行每行三个正整数 fr, to

[HAOI2015]树上操作 -树链剖分

1963. [HAOI2015]树上操作 [题目描述] 有一棵点数为N的树,以点1为根,且树点有权值.然后有M个操作,分为三种: 操作1:把某个节点x的点权增加a. 操作2:把某个节点x为根的子树中所有点的点权都增加a. 操作3:询问某个节点x到根的路径中所有点的点权和. [输入格式] 第一行两个整数N,M,表示点数和操作数. 接下来一行N个整数,表示树中节点的初始权值. 接下来N-1行每行两个正整数fr,to,表示该树中存在一条边(fr,to). 再接下来M行,每行分别表示一次操作.其中第一个

bzoj 4034: [HAOI2015]树上操作 树链剖分+线段树

4034: [HAOI2015]树上操作 Time Limit: 10 Sec  Memory Limit: 256 MBSubmit: 4352  Solved: 1387[Submit][Status][Discuss] Description 有一棵点数为 N 的树,以点 1 为根,且树点有边权.然后有 M 个 操作,分为三种: 操作 1 :把某个节点 x 的点权增加 a . 操作 2 :把某个节点 x 为根的子树中所有点的点权都增加 a . 操作 3 :询问某个节点 x 到根的路径中所有