1. 程式人生 > 其它 >Spices(線性基、貪心)

Spices(線性基、貪心)

P3178 [HAOI2015]樹上操作

分析

看題,是一道樹剖+線段樹裸題。不多說,貼一道板子P3384 【模板】輕重鏈剖分/樹鏈剖分。這就可以解決這道題目了。

我們要說的是另外一種方法。

這裡介紹一種不同於樹剖的方法,首先需要知道一個概念:尤拉序,這是 DFS 序的一種,舉個例子:

這樣的一棵樹,它的尤拉序為1 2 4 4 5 5 2 3 6 6 7 7 8 8 3 1

顯然,每個點在尤拉序中出現了 2 次;尤拉序有一個非常優越的性質,如果把每個點第一次出現記作 +,第二次出現記作 -,那麼根節點到任意節點的權值和在尤拉序上對應一個字首和,這個性質非常好理解,因為尤拉序其實又叫"出棧入棧序",所以字首中尚未抵消掉的點在 DFS 到當前點時在棧中,那麼其肯定在當前點到根的路徑中。

我們分別來說如何利用尤拉序來完成三個操作

用dfn1表示結點第一次出現的尤拉序中的編號,dfn2表示結點第二次出現的尤拉序中的編號

操作一

把某個節點x的權值增加a

這個操作分為兩個步驟,首先需要線上段樹樹上,第一次出現x的結點處+a,第二次出現x的結點處-a

我們加入一個數組num來統計從結點1開始到i中的+號的個數。

則,我們在進行修改操作的時候,對應結點處的符號可以直接通過num[i]-num[i-1]得到。

modify(1,dfn1[x],dfn1[x],c);
modify(1,dfn2[x],dfn2[x],c);

操作二

**把某個節點 x 為根的子樹中所有點的點權都增加 a **

其中需要更改的是一個區間,那這個區間中有些節點需要+a,有些節點需要-a(因為子樹中的結點被掃描的第一次和第二次結點標號都在區間中)。

那怎麼辦呢?

這時候我們只需要做一個小小的變化,其實一個區間受到 +a 的影響就是 \(a × (該區間內 + 的個數 - 該區間內 - 的個數)\)

modify(1,dfn1[x],dfn2[x],c);

操作三

詢問某個節點 x 到根的路徑中所有點的點權和。

這點就比較簡單了。

直接上程式碼

query(1,1,dfn1[x])

AC_code

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 1e5 + 10,M = N*2;
struct Node
{
    int l,r;
    LL add,sum;
}tr[N<<3];
struct DfsNode
{
    int v,f;
}dfspath[N<<1];
int h[N],e[M],ne[M],w[N],idx;
int dfn1[N],dfn2[N],num[N<<1],ts;
int n,m;

void add(int a,int b)
{
    e[idx] = b,ne[idx] = h[a],h[a] = idx++;
}

void dfs(int u,int fa)
{
    dfn1[u] = ++ts,dfspath[ts].f = 1;
    dfspath[ts].v = u;
    for(int i=h[u];~i;i=ne[i])
    {
        int j = e[i];
        if(j==fa) continue;
        dfs(j,u);
    }
    dfn2[u] = ++ts,dfspath[ts].f = -1;
    dfspath[ts].v = u;
}

void pushup(int u)
{
    tr[u].sum = tr[u<<1].sum + tr[u<<1|1].sum;
}

void pushdown(int u)
{
    auto &root = tr[u],&left = tr[u<<1],&right = tr[u<<1|1];
    if(root.add)
    {
        left.add += root.add;
        left.sum += (num[left.r] - num[left.l-1])*root.add;
        right.add += root.add;
        right.sum += (num[right.r] - num[right.l-1])*root.add;
        root.add = 0; 
    }
}

void build(int u,int l,int r)
{
    if(l==r)
    {
        tr[u] = {l,r,0,dfspath[l].f*w[dfspath[l].v]};
        return ;
    }
    tr[u] = {l,r,0,0};
    int mid = l + r >> 1;
    build(u<<1,l,mid),build(u<<1|1,mid+1,r);
    pushup(u);
}

void modify(int u,int l,int r,int k)
{
    if(l<=tr[u].l&&tr[u].r<=r)
    {
        tr[u].add += k;
        tr[u].sum += 1ll*k*(num[tr[u].r]-num[tr[u].l-1]);
        return ;
    }
    pushdown(u);
    int mid = tr[u].l + tr[u].r >> 1;
    if(l<=mid) modify(u<<1,l,r,k);
    if(r>mid) modify(u<<1|1,l,r,k);
    pushup(u);
}

LL query(int u,int l,int r)
{
    if(l<=tr[u].l&&tr[u].r<=r) return tr[u].sum;
    pushdown(u);
    int mid = tr[u].l + tr[u].r >> 1;
    LL res = 0;
    if(l<=mid) res += query(u<<1,l,r);
    if(r>mid) res += query(u<<1|1,l,r);
    pushup(u);
    return res;
}

int main()
{
    scanf("%d%d",&n,&m);
    memset(h,-1,sizeof h);
    for(int i=1;i<=n;i++) scanf("%d",&w[i]);
    for(int i=0;i<n-1;i++)
    {
        int a,b;scanf("%d%d",&a,&b);
        add(a,b),add(b,a);
    }
    dfs(1,-1);
    for(int i=1;i<=ts;i++)
        num[i] = num[i-1] + dfspath[i].f;
    build(1,1,2*n);
    while(m--)
    {
        int op,x,c;scanf("%d%d",&op,&x);
        if(op==1)
        {
            scanf("%d",&c);
            modify(1,dfn1[x],dfn1[x],c);
            modify(1,dfn2[x],dfn2[x],c);
        }
        else if(op==2)
        {
            scanf("%d",&c);
            modify(1,dfn1[x],dfn2[x],c);
        }
        else cout<<query(1,1,dfn1[x])<<endl;
    }
    return 0;
}