题解 P3178 【[HAOI2015]树上操作】

· · 题解

NOIP前一天还来水题解

竟然有一样脑回路新奇的题解。。只是不太明白为啥要用线段树维护Fenwicktree吼啊

首先把这个树的euler tour跑出来。首先考虑单点修改:

我们知道在欧拉序上一个点刚刚被访问要记录一次,离开会记录一次,于是我们在刚刚访问的位置+c,离开位置-c;

对于一个路径查询,我们需要计算一下1-这个点的前缀和,可以考虑树状数组。查询是进入还是离开的点呢?实际上都一样,因为进入离开之间所有点必定是两两配对的,所以算出来肯定是0.

考虑子树修改稍显麻烦,在一个点用一个树状数组记录+(dep[x]-1)*c(进入时加离开时减),

同时用一个等同于单点修改的记录一下+c,

当我们查询时,考虑所有加子树的贡献,就是
(u查询点)sum1是+c,sum0是+c*(dep[x-1])

sum1(in[u])*dep[u] - sum0(in[u])

然后考虑完所有单点加和子树加的贡献,就仅仅需要再计算一下初始到根的权值和就好。

代码非常简洁QWQ1.6k速度碾压树剖(实际上在此之前也写过一个树剖)

/*Heroes Never Die!*/
#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
using namespace std;
typedef long long ll;
#define MAXN 100006
struct edge{
    ll u;edge* next;
    edge( ll u = 0 , edge* next = NULL ):u(u),next(next) {}
    void insert( ll u ) {
        if( this->next == NULL ) this->next = new edge(u,NULL);
        else this->next = new edge( u,this->next );
    }
}e[MAXN];
ll in[MAXN],out[MAXN] , n , N , m , sdep[MAXN] , w[MAXN] , dep[MAXN];
ll S[3][MAXN<<1];//0 : 单点更值 , 1 值*dep 2 个数
void add( ll x , ll c , ll p ) { while( x <= N ) S[p][x] += c , x += x&-x; }
ll sum( ll x , ll p ) { ll res = 0; while( x > 0 ) res += S[p][x], x -= x & -x; return res; }
void addpoint( ll x ,ll c ) {
    add( in[x] , c , 0 ) , add( out[x] , -c , 0 );
}
void add_sub( ll x , ll c ) {
    add( in[x] , c*(dep[x]-1) , 1 ) , add( out[x] , -c*(dep[x]-1) , 1 ),
    add( in[x] , c , 2 ) , add( out[x] , -c , 2 );
}
ll query( ll x ) {
    return sdep[x] + sum(in[x],0) + dep[x] * sum( in[x],2 ) - sum(in[x],1);
}
void dfs( ll u , ll fa ) {
    in[u] = ++N;
    for( edge* n = e[u].next;n!=NULL;n=n->next ) {
        ll v = n->u;
        if( v == fa ) continue;
        sdep[v] = w[v] + sdep[u] , dep[v] = dep[u] + 1;
        dfs(v , u);
    }
    out[u] = ++N;
}
int main() {
    freopen("input","r",stdin);
    freopen("o","w",stdout);
    cin >> n >> m;
    for( ll i = 1  ; i <= n ; ++ i ) scanf("%lld",&w[i]);
    for( ll i = 0 ,u,v ; i < n - 1 ; ++ i )
        scanf("%lld%lld",&u,&v),e[u].insert(v),e[v].insert(u);
    sdep[1] = w[1] , dep[1] = 1;
    dfs( 1 , 1 );
    for( ll i = 0,opt,u,v ; i < m ; ++ i ){
        scanf("%lld%lld",&opt,&u);
        if( opt == 1 ) scanf("%lld",&v) , addpoint(u,v);
        else if( opt == 2 ) scanf("%lld",&v) , add_sub(u,v);
        else printf("%lld\n",query(u));
    }   
}
//qwq

考虑树剖的话,如果不抄板子我们知道只需要统计到根。。所以查询一个while就够了,具体看代码:

/*Heroes Never Die!*/
#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
typedef long long ll;
using namespace std;
#define MAXN 100006
struct edge{
    ll u;edge* next;
    edge(ll u=0,edge* next=NULL):u(u),next(next) {}
    void insert( ll u ) {
        if(this->next == NULL) this->next = new edge(u,NULL);
        else this->next = new edge(u,this->next);
    }
}e[MAXN];
ll siz[MAXN] , hea[MAXN] , pre[MAXN] , top[MAXN] , tig[MAXN] , bac[MAXN] , dep[MAXN],cnt;
ll w[MAXN] ;
ll n , m;
void dfs(ll u,ll fa) {
    siz[u] = 1;
    for( edge* n = e[u].next;n!=NULL;n=n->next ) {
        ll v = n->u;
        if(v == fa) continue;
        dep[v] = dep[u] + 1 , pre[v] = u;
        dfs(v,u);
        siz[u] += siz[v];
        if(!hea[u]||siz[hea[u]] < siz[v]) hea[u] = v;
    }
}
void dfs2(ll u,ll to) {
    tig[u] = ++cnt , bac[cnt] = u , top[u] = to;
    if( !hea[u] )return;
    dfs2(hea[u] , to);
    for( edge* n = e[u].next;n!=NULL;n=n->next ) {
        ll v = n->u;
        if( v == pre[u] || v == hea[u] ) continue;
        dfs2(v,v);
    }
}
ll S[MAXN<<2] , lazy[MAXN<<2];
void pushup( ll rt ) {
    S[rt] = S[rt<<1] + S[rt<<1|1];
}
void pushdown( ll rt , ll m ) {
    if( lazy[rt] ) {
        S[rt<<1] += lazy[rt] * ( m - (m>>1) ), 
        S[rt<<1|1] += lazy[rt] * ( m >> 1 ),
        lazy[rt<<1] += lazy[rt] , 
        lazy[rt<<1|1] += lazy[rt],
        lazy[rt] = 0;
    }
}
void build( ll rt ,ll l ,ll r ) {
    if( l == r ) { S[rt] = w[bac[l]] ; return; }
    ll m = l + r >> 1;
    build(rt<<1 , l , m);
    build(rt<<1|1 , m+1 , r);
    pushup( rt );
}
void update(ll rt,ll l,ll r,ll L,ll R,ll c){
    if( L <= l && R >= r ) { S[rt] += c*(r-l+1) , lazy[rt] += c; return; }
    ll m = l + r >> 1;
    pushdown(rt , r - l + 1);
    if( L <= m ) update( rt<<1 , l , m, L ,R, c );
    if( R >  m ) update( rt<<1|1, m+1 , r , L , R , c );
    pushup(rt);
}
ll query( ll rt, ll l,ll r,ll L,ll R ) {
    if( L <= l && R >= r ) return S[rt];
    pushdown(rt , r - l + 1);
    ll m = l + r >> 1, res = 0;
    if( L <= m ) res += query( rt<<1 , l , m , L , R );
    if( R >  m ) res += query( rt<<1|1 , m + 1, r , L, R );
    return res;
}
ll up( ll u ){
    ll res = 0;
    while( u ) {
        res += query( 1 , 1 , n , tig[top[u]] , tig[u] );
        u = pre[top[u]];
    }
    return res;
}
void add_point( ll u , ll c ) {
    update( 1 , 1 , n , tig[u] , tig[u] , c );
}
void add_sub( ll u , ll c ) {
    update( 1 , 1 , n , tig[u] , tig[u] + siz[u] - 1 , c );
}
int main() {
   // freopen("input","r",stdin);
//  freopen("o","w",stdout);
    cin >> n >> m;
    ll res = 0;
    for( ll i = 1 ; i <= n ; ++ i ) scanf("%lld",&w[i]),res += w[i];
    for( ll i = 0,u,v ; i < n-1 ; ++ i ) 
        scanf("%lld%lld",&u,&v) , e[u].insert(v), e[v].insert(u);
    dfs(1,1) , dfs2(1,1);
    build(1,1,n);
    for( ll i = 0,u,v,op ; i < m ; ++ i ) {
        scanf("%lld",&op);
        if( op == 1 ) scanf("%lld%lld",&u,&v),add_point(u,v);
        else if( op == 2 ) scanf("%lld%lld",&u,&v) , add_sub( u , v );
        else scanf("%lld",&u) , printf("%lld\n",up(u));
    }
}