Luogu P3833 [SHOI2012]魔法树

北战南征 提交于 2019-11-29 16:14:00

此题理论最优解

题目链接

题目大意:路径修改,子树求和

明显是树剖的模板,但树剖的时间复杂度达了优秀的$\Theta(Q \;log^2n)$,而实际上树上差分可以把时间复杂度降到$\Theta(Q \;logn)$。

设$tag[x]$为$1$到$x$的路径上全都加了这个值,显然对于$(x,y)$这条路径加$d$的操作可以看作
$$tag[x]+=d,tag[y]+=d,tag[LCA(x,y)]-=d,tag[prt[LCA(x,y)]]-=d$$

若查询以$root$为根的子树和,考虑子树内每个点$x$对答案的贡献是
$$(dep[x]-dep[root]+1)\times tag[x]$$
上式的意义是在$(x,root)$这条路径每个点都被加了$tag[x]$,对上式求和得到
$$\sum \;((dep[x]-dep[root]+1)\times tag[x])$$
$$\sum \;(dep[x]\times tag[x]-(dep[root]-1)\times tag[x])$$
$$\sum \;(dep[x]\times tag[x])-(dep[root]-1)\times\sum\;(tag[x])$$

两个树状数组维护$dep[x]\times tag[x]$和$tag[x]$就好了,因为在$DFS$序上子树是连续的区间,直接查询即可。

因为我的丑代码常数太大,把理论最优解跑还得不如树剖

#include<iostream>
#include<iomanip>
#include<cstring>
#include<cstdio>
#include<cmath>
#include<algorithm>
using namespace std;
#define int long long
inline int read() {
    char ch;
    bool bj=0;
    while(!isdigit(ch=getchar()))
        bj|=(ch=='-');
    int res=ch^(3<<4);
    while(isdigit(ch=getchar()))
        res=(res<<1)+(res<<3)+(ch^(3<<4));
    return bj?-res:res;
}
void printnum(int x) {
    if(x>9)printnum(x/10);
    putchar(x%10+'0');
}
inline void print(int x,char ch) {
    if(x<0) {
        putchar('-');
        x=-x;
    }
    printnum(x);
    putchar(ch);
}
const int MAXN=1e5+5;
int n,m,h[MAXN],cnt;
int top[MAXN],size[MAXN],prt[MAXN],son[MAXN],dep[MAXN],tot,tid[MAXN];
struct Edge {
    int to,nxt;
} w[MAXN<<1];
int c1[MAXN],c2[MAXN];
inline void AddEdge(int x,int y) {
    w[++cnt].nxt=h[x];
    w[cnt].to=y;
    h[x]=cnt;
}
void DFS1(int x,int fa,int depth) {
    dep[x]=depth;
    prt[x]=fa;
    size[x]=1;
    for(int i=h[x]; i; i=w[i].nxt) {
        int v=w[i].to;
        if(v==fa)continue;
        DFS1(v,x,depth+1);
        if(size[v]>size[son[x]])son[x]=v;
        size[x]+=size[v];
    }
}
void DFS2(int x,int sp) {
    top[x]=sp;
    tid[x]=++tot;
    if(!son[x])return;
    DFS2(son[x],sp);
    for(int i=h[x]; i; i=w[i].nxt) {
        int v=w[i].to;
        if(v==prt[x]||v==son[x])continue;
        DFS2(v,v);
    }
}
inline int LCA(int x,int y) {
    while(top[x]^top[y]) {
        if(dep[top[x]]<dep[top[y]])swap(x,y);
        x=prt[top[x]];
    }
    if(dep[x]>dep[y])swap(x,y);
    return x;
}
inline int lowbit(int x) {
    return x&(-x);
}
inline void update(int x,int d,int c[]) {
    if(!x)return;
    while(x<=n)c[x]+=d,x+=lowbit(x);
}
inline int query(int x,int c[]) {
    int sum=0;
    while(x)sum+=c[x],x-=lowbit(x);
    return sum;
}
inline void add(int x,int d) {
    update(tid[x],d,c1);
    update(tid[x],dep[x]*d,c2);
}
inline int ask(int x,int y,int c[]) {
    return query(y,c)-query(x-1,c);
}
signed main() {
    n=read();
    int x,y,d;
    for(int i=1; i<n; i++) {
        x=read()+1;
        y=read()+1;
        AddEdge(x,y);
        AddEdge(y,x);
    }
    DFS1(1,0,1);
    DFS2(1,1);
    m=read();
    char ch;
    while(m--) {
        do ch=getchar();
        while(ch!='A'&&ch!='Q');
        if(ch=='A') {
            x=read()+1;
            y=read()+1;
            d=read();
            int u=LCA(x,y);
            add(x,d);
            add(y,d);
            add(u,-d);
            add(prt[u],-d);
        } else {
            x=read()+1;
            int tmp=ask(tid[x],tid[x]+size[x]-1,c1);
            print(ask(tid[x],tid[x]+size[x]-1,c2)-tmp*(dep[x]-1),'\n');
        }
    }
    return 0;
}


为什么还是写了两个DFS?因为我要树剖求LCA

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!