[BZOJ 3306]树(dfs序+线段树+倍增)

Description

给定一棵大小为 n 的有根点权树,支持以下操作:

• 换根

• 修改点权

• 查询子树最小值

Solution

单点修改子树查询的话可以想到用dfs序+线段树来处理,换根的处理画一画图应该可以明白:

如果查询的x是当前的根rt,直接返回整棵树的min

如果rt在x的子树中,用倍增的方法找到离x最近的rt的祖先t,整棵树除t的子树以外的部分就是x当前根下的子树

如果rt不在x的子树中,查询x原来的子树的min值

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cstdlib>
#define MAXN 100005
#define INF 0x3f3f3f3f
using namespace std;
int n,q,head[MAXN],cnt=0,rt=0,w[MAXN],p[MAXN][20],deep[MAXN];
int a[MAXN],in[MAXN],out[MAXN],dfn_clock=0;
struct Node1
{
    int l,r,minn;
}t[MAXN*4];
struct Node2
{
    int next,to;
}Edges[MAXN*2];
void addedge(int u,int v)
{
    Edges[++cnt].next=head[u];
    head[u]=cnt;
    Edges[cnt].to=v;
}
int read()
{
    int x=0,f=1;char c=getchar();
    while(c<‘0‘||c>‘9‘){if(c==‘-‘)f=-1;c=getchar();}
    while(c>=‘0‘&&c<=‘9‘){x=x*10+c-‘0‘;c=getchar();}
    return x*f;
}
void dfs(int u)
{
    a[++dfn_clock]=u;in[u]=dfn_clock;
    for(int i=head[u];~i;i=Edges[i].next)
    {
        int v=Edges[i].to;
        if(v==p[u][0])continue;
        deep[v]=deep[u]+1;dfs(v);
    }
    out[u]=dfn_clock;
}
void init()
{
    for(int i=1;i<=16;i++)
    {
        for(int j=1;j<=n;j++)
        p[j][i]=p[p[j][i-1]][i-1];
    }
}
void build(int idx,int l,int r)
{
    t[idx].l=l,t[idx].r=r;
    if(l==r){t[idx].minn=w[a[l]];return;}
    int mid=(l+r)>>1;
    build(idx<<1,l,mid),build(idx<<1|1,mid+1,r);
    t[idx].minn=min(t[idx<<1].minn,t[idx<<1|1].minn);
}
void change(int idx,int p,int v)
{
    if(t[idx].l==t[idx].r){t[idx].minn=v;return;}
    int mid=(t[idx].l+t[idx].r)>>1;
    if(p<=mid)change(idx<<1,p,v);
    else change(idx<<1|1,p,v);
    t[idx].minn=min(t[idx<<1].minn,t[idx<<1|1].minn);
}
int query(int idx,int l,int r)
{
    if(l>r)return INF;
    if(l<=t[idx].l&&r>=t[idx].r)return t[idx].minn;
    int mid=(t[idx].l+t[idx].r)>>1;
    if(r<=mid)return query(idx<<1,l,r);
    else if(l>mid)return query(idx<<1|1,l,r);
    else return min(query(idx<<1,l,r),query(idx<<1|1,l,r));
}
int main()
{
    memset(head,-1,sizeof(head));
    n=read(),q=read();
    for(int i=1;i<=n;i++)
    {
        p[i][0]=read(),w[i]=read();
        if(!p[i][0])rt=i;
        addedge(p[i][0],i);
    }
    dfs(rt),init();
    build(1,1,n);
    for(int i=1;i<=q;i++)
    {
        char opt[2];int x,y;
        scanf("%s",opt);
        if(opt[0]==‘V‘)
        x=read(),y=read(),change(1,in[x],y);
        else if(opt[0]==‘E‘)
        {x=read();rt=x;}
        else
        {
            x=read();
            if(x==rt)printf("%d\n",query(1,1,n));
            else if(in[rt]>=in[x]&&out[rt]<=out[x])
            {
                int f=deep[rt]-deep[x]-1,t=rt;
                for(int j=0;j<=18;j++)
                if(f&(1<<j))t=p[t][j];
                printf("%d\n",min(query(1,1,in[t]-1),query(1,out[t]+1,n)));
            }
            else
            printf("%d\n",query(1,in[x],out[x]));
        }
    }
    return 0;
}
时间: 2024-08-05 19:31:26

[BZOJ 3306]树(dfs序+线段树+倍增)的相关文章

POJ 3321 DFS序+线段树

单点修改树中某个节点,查询子树的性质.DFS序 子树序列一定在父节点的DFS序列之内,所以可以用线段树维护. 1: /* 2: DFS序 +线段树 3: */ 4:   5: #include <cstdio> 6: #include <cstring> 7: #include <cctype> 8: #include <algorithm> 9: #include <vector> 10: #include <iostream> 1

codevs1228 (dfs序+线段树)

总结: 第一次遇到dfs序的问题,对于一颗树,记录节点 i 开始搜索的序号 Left[i] 和结束搜索的序号 Righti[i],那么序号在 Left[i] ~ Right[i] 之间的都是节点 i 子树上的节点. 并且此序号与线段树中 L~R 区间对应,在纸上模拟了几遍确实如此,但暂时还未理解为何对应. 此题就是dfs序+线段树的裸题 代码: #include<iostream> #include<vector> #include<cstring> #include&

Educational Codeforces Round 6 E dfs序+线段树

题意:给出一颗有根树的构造和一开始每个点的颜色 有两种操作 1 : 给定点的子树群体涂色 2 : 求给定点的子树中有多少种颜色 比较容易想到dfs序+线段树去做 dfs序是很久以前看的bilibili上电子科技大学发的视频学习的 将一颗树通过dfs编号的方式 使每个点的子树的编号连在一起作为相连的区间 就可以配合线段树搞子树 因为以前好像听说过 线段树可以解决一种区间修改和查询区间中不同的xx个数...所以一下子就想到了... 但是我不会写线段树..只会最简单的单点修改区间查询...不会用延迟标

【BZOJ-3252】攻略 DFS序 + 线段树 + 贪心

3252: 攻略 Time Limit: 10 Sec  Memory Limit: 128 MBSubmit: 339  Solved: 130[Submit][Status][Discuss] Description 题目简述:树版[k取方格数] 众所周知,桂木桂马是攻略之神,开启攻略之神模式后,他可以同时攻略k部游戏. 今天他得到了一款新游戏<XX半岛>,这款游戏有n个场景(scene),某些场景可以通过不同的选择支到达其他场景.所有场景和选择支构成树状结构:开始游戏时在根节点(共通线)

【XSY2667】摧毁图状树 贪心 堆 DFS序 线段树

题目大意 给你一棵有根树,有\(n\)个点.还有一个参数\(k\).你每次要删除一条长度为\(k\)(\(k\)个点)的祖先-后代链,问你最少几次删完.现在有\(q\)个询问,每次给你一个\(k\),问你答案是多少. \(n\leq {10}^5,k\leq {10}^9\) 题解 设\(l\)为这棵树的叶子个数,显然当\(k>\)树的深度时答案都是\(l\). 下面要证明:答案是\(O(l+\frac{n-l}{k})\)的. 我们从下往上贪心,每次选择一个未被覆盖的深度最深的点,覆盖这个点网

codeforces 343D Water Tree 树链剖分 dfs序 线段树 set

题目链接 这道题主要是要考虑到同一棵子树中dfs序是连续的 然后我就直接上树剖了... 1 #include<bits/stdc++.h> 2 using namespace std; 3 const int MAXN=600005; 4 5 struct Node 6 { 7 int l,r; 8 int value; 9 void init() 10 { 11 l=r=value=0; 12 } 13 }tree[4*MAXN]; 14 vector<int>nei[MAXN]

BZOJ.4817.[SDOI2017]树点涂色(LCT DFS序 线段树)

题目链接 1.2裸树剖,但是3.每个点的答案val很不好维护.. 如果我们把同种颜色的点划分到同一连通块中,那么向根染色的过程就是Access()! 最初所有点间都是虚边,相同颜色点用实边相连.一条边由实边变为虚边时,深度大的点所在子树所有点val+1(Access()中原先x的右儿子答案+1,因为x颜色变了): 由虚边变为实边时,深度大的点所在子树所有点val-1(fa[x]颜色与x相同导致fa[x]的贡献没了).(其实是因为 实链数量(贡献)就等于虚边数量+1?无所谓了) 于是2.就是val

Manthan, Codefest 16(G. Yash And Trees(dfs序+线段树))

题目链接:点击打开链接 题意:给你一棵树, 根结点为1, q组操作, 每组操作有两种, 一种是对一个结点的所有子树结点的值全部+1, 另一种是查询一个结点的子树结点上值%m的余数为素数的个数. 思路:对于第一个操作, 我们可以想到用dfs序给树重新标号, 使得一个结点的子树结点为相邻的一条线段, 这样,就可以很容易的用线段树进行处理了.  对于第二个操作, 为了维护一个区间内的值, 我们可以用bitset作为结点信息.  我们可以开一个m位的bitset, 对于每个位, 1表示这个数在此区间中,

SDUTOJ1755 装备合成(dfs序+线段树)

题目描述 小白很喜欢玩儿LOL,但是无奈自己是个坑货,特别是在装备的选择和合成上,总是站在泉水里为选装备而浪费时间.现在小白试图解决这个问题,为了使问题简单化,我们把游戏中的装备合成规则简化如下: (1)装备的合成关系构成一棵合成关系树,如图(a)所示,装备的下级装备叫合成小件,直接连接的叫直接合成小件:上级装备叫合成件,直接连接的叫直接合成件.合成树上的每件装备都可以直接购买,如果钱不够也可以先购买这个装备的合成小件. (2)每个装备都有一个合成价格和一个总价格,装备的总价格=该装备的合成价格