CodeForces - 384E Propagating tree(DFS序 + 二分 + 线段树)

32 篇文章 0 订阅
27 篇文章 0 订阅

题意:

给你一棵树, 两种操作:

1. 将结点x  所有后代进行加和val,  x 结点加 val  ,x 孩子 加(-val)  x 孩子的孩子加 -(-val), 以此类推。

2. 求结点x 的权值。

思路:

比赛时没啥好思路,就做别的题去了, 但总感觉这就是线段树。


果真没错。 其实挺水的= =

整体思路:

先将树dfs序跑一遍, 使得每个点变成连续的。

将树分成两类, 奇数深度的一类, 偶数深度的一类。

把这两类分别建线段树。

这样就成了两棵树上的 区间增减,单点查询的问题。


在说说细节。

比如说 要将结点x  增加val, 

我们先算出 这个结点的最后一个孩子的编号。

假设得到了一个区间[L,R]

然后怎么分别 两个线段树 增减呢。

dfs序编号完毕后,  依次分类, 这样两类都是编号递增的。

这样,我们就可以用二分来找两类的线段树的边界了。


在说说更细的细节。

就是cf  向来以边界恶心素称。

1.他可能会有 只有一类的线段树的情况。(比如只有一个结点)

2. 也可能每次更新, 只更新 一类线段树。  分好类就好了。


#include <cstdio>
#include <cstring>
#include <algorithm>
#include <vector>
#define Siz(x) (int)x.size()
using namespace std;


const int maxn = 200000 + 10;


vector<int>g[maxn];

int id[maxn], f[maxn], a[maxn], jud[maxn];

int cnt;

int ed[maxn];

vector<int> v[2];



struct SEG_TREE{
    struct node{
        int l,r;
        int addr;
    }nod[maxn<<2];

    int id_;
    int exist;

    void pushdown(int o){
        if (nod[o].addr){
            nod[o<<1].addr += nod[o].addr;
            nod[o<<1|1].addr += nod[o].addr;
            nod[o].addr = 0;
        }
    }

    void build(int l,int r,int o){
        nod[o].l = l;
        nod[o].r = r;
        nod[o].addr = 0;
        if (l == r){
            return;
        }
        int m = l + r >> 1;
        build(l, m, o<<1);
        build(m+1,r,o<<1|1);
    }

    void update(int L,int R,int c,int l,int r,int o){
        if (L <= l && r <= R){
            nod[o].addr += c;
            return;
        }
        int m = l + r >> 1;
        pushdown(o);
        if (m >= L){
            update(L,R,c,l,m,o<<1);
        }
        if (m < R){
            update(L,R,c,m+1,r,o<<1|1);
        }
    }

    int query(int pos,int l,int r,int o){
        if (l == r){
            return nod[o].addr + a[f[v[id_][l] ] ];
        }
        int m = l + r >> 1;
        pushdown(o);
        int lson = o << 1;
        int rson = o << 1 | 1;
        if (v[id_][m] >= pos){
            return query(pos,l, m, lson);
        }
        else {
            return query(pos, m+1, r, rson);
        }
    }
}st[2];
/// st[0] 是偶数深度的线段树 ,  st[1] 是奇数深度线段树。
bool is_leaf[maxn];
void dfs(int cur,int fa,int dep){
    id[cur] = ++cnt;

    f[cnt] = cur;

    v[dep&1].push_back(cnt);

    jud[cnt] = dep&1;
    bool ok = 0;
    for (int i = 0; i < g[cur].size(); ++i){
        int v = g[cur][i];
        if (v != fa){
            dfs(v, cur, dep+1);
            ok = 1;
        }
    }
    if (!ok) is_leaf[cnt] = 1;
    ed[cur] = cnt;
}

void solve(int x,int val){
    int L = id[x];
    int R = ed[x];
    int i = jud[L];

    int l = 0,r = Siz(v[i])-1, m;
    int pos1, pos2,pos3,pos4; /// 二分找边界。
    while(l <= r){
        m = l + r >> 1;
        if (v[i][m] == L){
            pos1 = m;
            break;
        }
        else if (v[i][m] > L){
            r = m - 1;
        }
        else l = m + 1;
    }
    l = 0 ; r = Siz(v[i])-1;
    while(l <= r){
        m = l + r >> 1;
        if (v[i][m] <= R){
            l = m + 1;
        }
        else r = m-1;
    }
    pos2 = r;
    i ^= 1;
    l = 0;r = Siz(v[i])-1;
    if (is_leaf[id[x] ])goto TT;
    while(l <= r){
        m = l + r >> 1;
        if (v[i][m] >= L){
            r = m - 1;
        }
        else l = m + 1;
    }
    pos3 = l;
    l = 0;r = Siz(v[i])-1;
    while(l <= r){
        m = l + r >> 1;
        if (v[i][m] <= R){
            l = m + 1;

        }
        else r = m -1;

    }
    pos4 = r;
//    printf("%d %d : %d %d %d %d %d\n", L,R, pos1, pos2, pos3, pos4,val);
//    puts("update:");
//    printf("%d:%d %d %d\n", i^1,pos1, pos2, val);
//    printf("%d:%d %d %d\n", i,pos3, pos4, -val);
    TT:
    if (st[i^1].exist)st[i^1].update(pos1, pos2, val, 0, Siz(v[i^1]) - 1, 1);
    if (!is_leaf[id[x] ] && st[i].exist)st[i].update(pos3, pos4, -val, 0, Siz(v[i]) - 1, 1);
}


int main(){

    int n,q;
    scanf("%d %d",&n, &q);
    for (int i = 1; i <= n; ++i){
        scanf("%d",&a[i]);
    }

    for (int i = 1; i < n; ++i){
        int u,v;
        scanf("%d %d",&u, &v);
        g[u].push_back(v);
        g[v].push_back(u);
    }


    dfs(1, -1, 0);

    for (int i = 0; i < 2; ++i){
        st[i].id_ = i;
        st[i].exist = 0; /// 用来处理边界的。= =
        if (Siz(v[i])){
            st[i].build(0, Siz(v[i])-1, 1);
            st[i].exist = 1;
        }
    }



    int op, x, val;
    while(q--){
        scanf("%d%d",&op,&x);
        if (op == 1){

            scanf("%d",&val);
            solve(x, val);
        }
        else {
            int i = jud[id[x]];
            int ans = st[i].query(id[x], 0, Siz(v[i]) - 1, 1);
            printf("%d\n", ans);
        }
    }
    return 0;
}




E. Propagating tree
time limit per test
2 seconds
memory limit per test
256 megabytes
input
standard input
output
standard output

Iahub likes trees very much. Recently he discovered an interesting tree named propagating tree. The tree consists of n nodes numbered from 1 to n, each node i having an initial value ai. The root of the tree is node 1.

This tree has a special property: when a value val is added to a value of node i, the value -val is added to values of all the children of node i. Note that when you add value -val to a child of node i, you also add -(-val) to all children of the child of node i and so on. Look an example explanation to understand better how it works.

This tree supports two types of queries:

  • "1 x val" — val is added to the value of node x;
  • "2 x" — print the current value of node x.

In order to help Iahub understand the tree better, you must answer m queries of the preceding type.

Input

The first line contains two integers n and m (1 ≤ n, m ≤ 200000). The second line contains n integers a1a2, ..., an (1 ≤ ai ≤ 1000). Each of the next n–1 lines contains two integers vi and ui (1 ≤ vi, ui ≤ n), meaning that there is an edge between nodes vi and ui.

Each of the next m lines contains a query in the format described above. It is guaranteed that the following constraints hold for all queries: 1 ≤ x ≤ n, 1 ≤ val ≤ 1000.

Output

For each query of type two (print the value of node x) you must print the answer to the query on a separate line. The queries must be answered in the order given in the input.

Examples
input
5 5
1 2 1 1 2
1 2
1 3
2 4
2 5
1 2 3
1 1 2
2 1
2 2
2 4
output
3
3
0
Note

The values of the nodes are [1, 2, 1, 1, 2] at the beginning.

Then value 3 is added to node 2. It propagates and value -3 is added to it's sons, node 4 and node 5. Then it cannot propagate any more. So the values of the nodes are [1, 5, 1,  - 2,  - 1].

Then value 2 is added to node 1. It propagates and value -2 is added to it's sons, node 2 and node 3. From node 2 it propagates again, adding value 2 to it's sons, node 4 and node 5. Node 3 has no sons, so it cannot propagate from there. The values of the nodes are [3, 3,  - 1, 0, 1].

You can see all the definitions about the tree at the following link: http://en.wikipedia.org/wiki/Tree_(graph_theory)


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
引用\[1\]中提到了一种树形动态规划的方法来解决CodeForces - 982C问题。在这个问题中,subtree指的是子连通块,而不是子树。为了使cnt_white - cnt_black尽可能大,可以使用两次树形动态规划来求解。第一次是自底向上的过程,维护一个dp数组,表示以每个节点为根的子树中的最大连通块。第二次是自顶向下的过程,处理自底向上过程中无法包含的树链所代表的子树。在第二次遍历中,需要维护一个sum变量,用于存储树链所代表的子树的贡献。根据ans\[u\]的正负,决定是否能对相邻的子节点做出贡献。如果ans\[u\]为正,则减去dp\[v\]就是树链所代表的子树的权值。最终,ans\[u\]代表包含节点u在内的子连通块的最大权值。\[1\] 问题: CodeForces - 982C 树形DP是什么问题?如何解决? 回答: CodeForces - 982C是一个树形动态规划问题。在这个问题中,需要求解子连通块的最大权值和,使得cnt_white - cnt_black尽可能大。解决这个问题的方法是使用两次树形动态规划。第一次是自底向上的过程,维护一个dp数组,表示以每个节点为根的子树中的最大连通块。第二次是自顶向下的过程,处理自底向上过程中无法包含的树链所代表的子树。在第二次遍历中,需要维护一个sum变量,用于存储树链所代表的子树的贡献。根据ans\[u\]的正负,决定是否能对相邻的子节点做出贡献。最终,ans\[u\]代表包含节点u在内的子连通块的最大权值。\[1\] #### 引用[.reference_title] - *1* *2* [CodeForces - 1324F Maximum White Subtree(树形dp)](https://blog.csdn.net/qq_45458915/article/details/104831678)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^koosearch_v1,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值