CodeForces 838B - Diverging Directions - [DFS序+线段树]

题目链接:http://codeforces.com/problemset/problem/838/B

You are given a directed weighted graph with n nodes and 2n?-?2 edges. The nodes are labeled from 1 to n, while the edges are labeled from 1 to 2n?-?2. The graph‘s edges can be split into two parts.

  • The first n?-?1 edges will form a rooted spanning tree, with node 1 as the root. All these edges will point away from the root.
  • The last n?-?1 edges will be from node i to node 1, for all 2?≤?i?≤?n.

You are given q queries. There are two types of queries

  • i w: Change the weight of the i-th edge to w
  • u v: Print the length of the shortest path between nodes u to v

Given these queries, print the shortest path lengths.

Input

The first line of input will contain two integers n,?q (2?≤?n,?q?≤?200?000), the number of nodes, and the number of queries, respectively.

The next 2n?-?2 integers will contain 3 integers ai,?bi,?ci, denoting a directed edge from node ai to node bi with weight ci.

The first n?-?1 of these lines will describe a rooted spanning tree pointing away from node 1, while the last n?-?1 of these lines will have bi?=?1.

More specifically,

  • The edges (a1,?b1),?(a2,?b2),?... (an?-?1,?bn?-?1) will describe a rooted spanning tree pointing away from node 1.
  • bj?=?1 for n?≤?j?≤?2n?-?2.
  • an,?an?+?1,?...,?a2n?-?2 will be distinct and between 2 and n.

The next q lines will contain 3 integers, describing a query in the format described in the statement.

All edge weights will be between 1 and 106.

Output

For each type 2 query, print the length of the shortest path in its own line.

Example

Input

5 91 3 13 2 21 4 33 5 45 1 53 1 62 1 74 1 82 1 12 1 32 3 52 5 21 1 1002 1 31 8 302 4 22 2 4

Output

014810013210

题意:

给出n个节点,2n-2条边(有向带权);

其中前n-1条边使得n个点构成一棵带权有向树;后n-1条边,权重为w,是从2~n号节点直接连接向1号节点的;

现在给出两种操作:

①修改第 i 条边的权重为w;

②查询节点u和v之间最短路径的权重和;

题解:

先忽略后n-1条边,DFS序拍平整棵树;

同时在DFS时,顺便计算出节点i的dist[i],代表从节点1到节点i的唯一的一条路径的权重和;

记录每个节点的 dist[i] + Edge(i→1).weight,用线段树在DFS序上维护区间最小值;

then……

对于修改操作:

  ①若修改的边是Edge(x→1)类型的,那么更新区间[ in[x] , in[x] ]上的值;

  ②否则,更新区间[ in[x] , out[x] ]上的值;

对于查询操作:

  ①v在u统领的子树内,按道理来讲直接dist[v]-dist[u]即可,

   但是我们在修改边权时不对dist[]数组进行更新,所以要通过式子dist[x] = query([ in[x] , in[x] ]) - Edge(x→1).weight来求得dist[u]和dist[v];

  ②v不在u统领的子树内,那么只能通过 u → … → 1 → … → v 这样的路径从u走到v,

   显然,u统领的子树内的每个节点,都能使得从节点u出发回到节点1,

   那么我们就query([ in[u] , out[u] ])查:u统领的这颗子树内的哪个节点,它的 dist[x] + Edge(x→1).weight 是最小的;

   一旦查到,ans = query([ in[u] , out[u] ]) - dist[u] + dist[v],同样的道理,这里的dist[u]和dist[v]都要像上面那样通过式子 dist[x] = query([ in[x] , in[x] ]) - Edge(x→1).weight 求得。

AC代码:

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int maxn=200000+10;
const LL INF=1e18;

int n,q;

//邻接表
struct Edge{
    int u,v;
    LL w;
    Edge(int u,int v,LL w)
    {
        this->u=u;
        this->v=v;
        this->w=w;
    }
};
vector<Edge> E; int E_size;
vector<int> G[maxn];
int ptr[2*maxn];
void adjListInit(int l,int r)
{
    E.clear(); E_size=0;
    for(int i=l;i<=r;i++) G[i].clear();
}
void addEdge(int u,int v,LL w,int i)
{
    E.push_back(Edge(u,v,w)); E_size++;
    ptr[i]=E_size-1;
    G[u].push_back(E_size-1);
}

//存储所有返回到1的边(即编号为n~2n-2的边)
vector<Edge> Eback;
int Gback[maxn];
int Eback_size;

//DFS建立DFS序列,以及计算深度
LL dist[maxn];
int in[maxn],out[maxn];
int peg[maxn];
int dfs_clock;
inline void dfsInit()
{
    dist[1]=0;
    dfs_clock=0;
}
void dfs(int now,int par)
{
    //printf("now=%d\n",now);
    in[now]=++dfs_clock;
    peg[in[now]]=now;

    for(int i=0,_size=G[now].size();i<_size;i++)
    {
        Edge &e=E[G[now][i]]; int nxt=e.v;
        if(nxt!=par)
        {
            dist[nxt]=dist[now]+e.w;
            dfs(nxt,now);
        }
    }

    out[now]=dfs_clock;
}

//线段树
struct Node{
    int l,r;
    LL val,lazy;
    void update(LL x)
    {
        val+=x;
        lazy+=x;
    }
}node[4*maxn];
void pushdown(int root)
{
    if(node[root].lazy)
    {
        node[root*2].update(node[root].lazy);
        node[root*2+1].update(node[root].lazy);
        node[root].lazy=0;
    }
}
void pushup(int root)
{
    node[root].val=min(node[root*2].val,node[root*2+1].val);
}
void build(int root,int l,int r)
{
    node[root].l=l; node[root].r=r;
    node[root].val=0; node[root].lazy=0;
    if(l==r)
    {
        int p=peg[l]; //从DFS序中的位置逆向查节点编号
        node[root].val=dist[p]+Eback[Gback[p]].w;
    }
    else
    {
        int mid=l+(r-l)/2;
        build(root*2,l,mid);
        build(root*2+1,mid+1,r);
        pushup(root);
    }
}
void update(int root,int st,int ed,int val)
{
    if(st>node[root].r || ed<node[root].l) return;
    if(st<=node[root].l && node[root].r<=ed) node[root].update(val);
    else
    {
        pushdown(root);
        update(root*2,st,ed,val);
        update(root*2+1,st,ed,val);
        pushup(root);
    }
}
LL query(int root,int st,int ed)
{
    if(ed<node[root].l || node[root].r<st) return INF;
    if(st<=node[root].l && node[root].r<=ed) return node[root].val;
    else
    {
        pushdown(root);
        LL lson=query(root*2,st,ed);
        LL rson=query(root*2+1,st,ed);
        pushup(root);
        return min(lson,rson);
    }
}

int main()
{
    cin>>n>>q;

    adjListInit(1,n);
    for(int i=1;i<=n-1;i++)
    {
        int a,b; LL c;
        scanf("%d%d%I64d",&a,&b,&c);
        addEdge(a,b,c,i);
    }

    dfsInit();
    dfs(1,-1);
    //for(int i=1;i<=n;i++) printf("in[%d]=%d  out[%d]=%d\n",i,in[i],i,out[i]);

    Eback.clear(); Eback_size=0;
    for(int i=n,a,b,c;i<=2*n-2;i++)
    {
        scanf("%d%d%d",&a,&b,&c);
        Eback.push_back(Edge(a,b,c)); Eback_size++;
        ptr[i]=Eback_size-1;
        Gback[a]=Eback_size-1;
    }
    Eback.push_back(Edge(1,1,0)); Eback_size++;
    Gback[1]=Eback_size-1;

    build(1,1,n);
    for(int i=1,type;i<=q;i++)
    {
        scanf("%d",&type);
        if(type==1)
        {
            int id; LL w;
            scanf("%d%I64d",&id,&w);
            if(id<n)
            {
                Edge &e=E[ptr[id]];
                //printf("Edge %d: %d->%d = %I64d\n",id,e.u,e.v,e.w);
                update(1,in[e.v],out[e.v],w-e.w);
                e.w=w;
            }
            else
            {
                Edge &e=Eback[ptr[id]];
                //printf("Edge %d: %d->%d = %I64d\n",id,e.u,e.v,e.w);
                update(1,in[e.u],in[e.u],w-e.w);
                e.w=w;
            }
        }
        if(type==2)
        {
            int u,v;
            scanf("%d%d",&u,&v);
            if(in[u]<=in[v] && in[v]<=out[u]) //v在u统领的子树内
            {
                //printf("%d是%d的祖先\n",u,v);
                LL du=query(1,in[u],in[u])-Eback[Gback[u]].w;
                LL dv=query(1,in[v],in[v])-Eback[Gback[v]].w;
                printf("%I64d\n",dv-du);
            }
            else
            {
                //printf("%d不是%d的祖先\n",u,v);
                LL ans=query(1,in[u],out[u]);

                LL du=query(1,in[u],in[u])-Eback[Gback[u]].w;
                LL dv=query(1,in[v],in[v])-Eback[Gback[v]].w;
                ans-=du;
                ans+=dv;

                printf("%I64d\n",ans);
            }
        }
    }
}

PS.第一次200+行的代码1A,还是值得小小窃喜一下的

原文地址:https://www.cnblogs.com/dilthey/p/9005129.html

时间: 2024-08-17 20:05:56

CodeForces 838B - Diverging Directions - [DFS序+线段树]的相关文章

Educational Codeforces Round 6 E dfs序+线段树

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

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]

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&

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

Description 给定一棵大小为 n 的有根点权树,支持以下操作: • 换根 • 修改点权 • 查询子树最小值 Solution 单点修改子树查询的话可以想到用dfs序+线段树来处理,换根的处理画一画图应该可以明白: 如果查询的x是当前的根rt,直接返回整棵树的min 如果rt在x的子树中,用倍增的方法找到离x最近的rt的祖先t,整棵树除t的子树以外的部分就是x当前根下的子树 如果rt不在x的子树中,查询x原来的子树的min值 #include<iostream> #include<

【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})\)的. 我们从下往上贪心,每次选择一个未被覆盖的深度最深的点,覆盖这个点网

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

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

Codeforces 620E New Year Tree(DFS序+线段树)

题目大概说给一棵树,树上结点都有颜色(1到60),进行下面两个操作:把某结点为根的子树染成某一颜色.询问某结点为根的子树有多少种颜色. 子树,显然DFS序,把子树结点映射到连续的区间.而注意到颜色60种,这样就可以用一个64位整型去表示颜色的集合,然后就是在这个连续区间中用线段树成段更新颜色集合和区间查询颜色集合了. 1 #include<cstdio> 2 #include<cstring> 3 using namespace std; 4 #define MAXN 500000