2020牛客暑假多校第七场补题

比赛链接:link

题目


   

C 树剖

   题目大意是说对于一棵树,初始所有点的权值 F ( x ) F(x) F(x) 为0,有三种操作:①对于结点 x x x, 给定一个值 w w w,然后对于树上所有结点 y y y, 权值加上 w − d i s t ( x , y ) w - dist(x, y) wdist(x,y)(包括本身);② 对于结点 x , F ( x ) = m i n ( x , 0 ) x, F(x) = min(x, 0) xF(x)=min(x,0);③询问 F ( x ) F(x) F(x)
   对于操作①的转化很巧妙: w − d i s t ( x , y ) = w − d e p ( x ) − d e p ( y ) + 2 d e p ( l c a ( x , y ) ) w - dist(x, y) = w - dep(x) - dep(y) + 2dep(lca(x,y)) wdist(x,y)=wdep(x)dep(y)+2dep(lca(x,y)),其中每次①操作的 w − d e p ( x ) w - dep(x) wdep(x) 都可以记录下来, d e p ( y ) dep(y) dep(y) 也是一个定值,而对于 d e p ( l c a ( x , y ) ) dep(lca(x, y)) dep(lca(x,y)), 可以用树剖来记录,若记根节点 r t rt rt 深度为 1,只需要让结点 x x x 到根结点路径上的结点加上 2, d e p ( l c a ( x , y ) ) dep(lca(x, y)) dep(lca(x,y))就是结点 y y y 到根结点的权值和。
   然后对于操作②,若是某个结点的权值大于 0, 我们可以用数组去记录减去的数是多少。
   刚学的树剖模板,千万不要忘记初始化

#include <bits/stdc++.h>
#define pb push_back

using namespace std;

typedef long long ll;
typedef pair<int, int> P;
const int maxn = 5e4 + 10;
const int INF = 0x3f3f3f3f;
const ll mod = 998244353;

int n, m;

struct edge                     //链式前向星
{
    int to, next;
}e[maxn*2];

int head[maxn], num;         //head为0表示搜索到了尽头

void add_edge(int u, int v)
{
    e[++num].to = v;
    e[num].next = head[u];
    head[u] = num;
}

ll tree[maxn<<2], lazy[maxn<<2], dfn[maxn];                 //线段树模板

void build(int node, int l, int r)
{
    if(l == r)
    {
        tree[node] = 0;
        lazy[node] = 0;
        return;
    }
    int mid =  (l + r) / 2;
    build(node * 2, l, mid);
    build(node * 2 + 1, mid + 1, r);
    tree[node] = 0;
    lazy[node] = 0;
}

void push_down(int node, int length)
{
    if(lazy[node])
    {
        tree[2 * node] += lazy[node] * ((length + 1) / 2);   //向子节进行更新
        tree[2 * node + 1] += lazy[node] * (length / 2);
        lazy[2 * node] += lazy[node];                        //标记子节点
        lazy[2 * node + 1] += lazy[node];
        lazy[node] = 0;                                      //父节点标记清空
    }
}

ll query(int node, int l, int r, int x, int y)
{
    if(x <= l && y >= r)
        return tree[node];

    push_down(node, r  - l + 1);           //多的地方

    int mid = (l + r) / 2;
    ll ans = 0LL;
    if(x <= mid) ans += query(node * 2, l, mid, x, y);
    if(y > mid) ans += query(node * 2 + 1, mid + 1, r, x, y);
    return ans;
}

void update(int node, int l, int r, int x, int y, int c)
{
    if(x <= l && y >= r)
    {
        tree[node] += (r - l + 1) * c;
        lazy[node] += c;
        return;
    }

    push_down(node, r - l + 1);

    int mid = (l + r) / 2;
    if(x <= mid) update(2*node, l, mid, x, y, c);
    if(y > mid) update(2 * node + 1, mid + 1, r, x, y, c);
    tree[node] = tree[node * 2] + tree[node * 2 + 1];
}

int siz[maxn], son[maxn], top[maxn], dep[maxn], faz[maxn], id[maxn], tol;

void dfs1(int x)
{
    siz[x] = 1;
    son[x] = 0;
    for(int i = head[x]; i; i = e[i].next)
    {
        int v = e[i].to;
        if(v == faz[x])  continue;
        dep[v] = dep[x] + 1;             //标记深度
        faz[v] = x;                      //标记父亲
        dfs1(v);
        siz[x] += siz[v];                //记录子树个数
        if(siz[v] > siz[son[x]])  son[x] = v;          //标记重儿子
    }
}

void dfs2(int x, int rt)
{
    id[x] = ++tol;                    //记录在dfs序中的编号
    dfn[tol] = x;
    top[x] = rt;                       //记录所在链深度最小的结点
    if(son[x])  dfs2(son[x], rt);
    for(int i = head[x]; i; i = e[i].next)
    {
        int v = e[i].to;
        if(v == faz[x] || v == son[x])  continue;
        dfs2(v, v);
    }
}

void updRange(int u, int v, int val)
{
    while(top[u] != top[v])               //当两个点不在一条链上
    {
        if(dep[top[u]] < dep[top[v]])  swap(u, v);  //让u为所在链顶端深度更大的那个点
        update(1, 1, n, id[top[u]], id[u], val);
        u = faz[top[u]];
    }
    if(dep[u] > dep[v]) swap(u, v);     //让u为深度浅的那个点
    update(1, 1, n, id[u], id[v], val);
}

ll qRange(int u, int v)
{
    ll ans = 0;
    while(top[u] != top[v])               //当两个点不在一条链上
    {
        if(dep[top[u]] < dep[top[v]])  swap(u, v);  //让u为所在链顶端深度更大的那个点
        ans +=  query(1, 1, n, id[top[u]], id[u]);
        u = faz[top[u]];
    }
    if(dep[u] > dep[v]) swap(u, v);     //让u为深度浅的那个点
    ans += query(1, 1, n, id[u], id[v]);
    return ans;
}

ll pre[maxn];

int main()
{
    int t, mark, x, w;
    scanf("%d", &t);
    while(t--)
    {
        scanf("%d %d", &n, &m);
        fill(head + 1, head + 1 + n, 0);
        fill(pre + 1, pre + 1 + n, 0);
        num = 0; tol = 0;
        for(int i = 1; i < n; i++)
        {
            int x, y;
            scanf("%d %d", &x, &y);
            add_edge(x, y);  add_edge(y, x);
        }

        dep[1] = 1;
        dfs1(1);
        dfs2(1, 1);
        build(1, 1, n);
        int cnt = 0;
        ll cur = 0, tmp;
        for(int i = 1; i <= m; i++)
        {
            scanf("%d", &mark);
            if(mark == 1)
            {
                scanf("%d %d", &x, &w);
                cur += w - dep[x];
                cnt++;
                updRange(1, x, 2);
            }
            else if(mark == 2)
            {
                scanf("%d", &x);
                tmp = cur - cnt * dep[x] + qRange(1, x) + pre[x];
                if(tmp > 0)
                    pre[x] -= tmp;
            }
            else
            {
                scanf("%d", &x);
                tmp = cur - cnt * dep[x] + qRange(1, x) + pre[x];
                printf("%lld\n", tmp);
            }
        }
    }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值