P3038 [USACO11DEC]Grass Planting G(樹鏈剖分邊權轉點權)

小菜雞加油發表於2020-11-20

https://www.luogu.com.cn/problem/P3038


給出一棵n個節點的樹,有m個操作,操作為將一條路徑上的邊權加一或詢問某條邊的權值


思路:邊權轉點權

過程:參考羅老師

如果把邊權轉為點權,就能按前面給出的“樹鏈剖分 + 線段樹”來解決。
  例如下圖(1),若把邊權轉為點權,顯然只能把每條邊上的邊權賦給這條邊下層的結點,得到圖(2)。程式設計操作是:比較邊(u, v)的兩點的deep[u]、deep[v],把邊權賦給更深的那個結點。

圖4 圖(1)邊權樹        (2)轉化為點權樹

  轉換為點權後,樹剖的操作基本上一樣。但是,區間求和和區間更新操作都有一點問題。
  (1)區間求和。例如圖(1)求從d到e的路徑,d-b-e的長度是7 + 3 = 10;但是圖(2)變成了7 + 2 + 3 = 12,多算了b點的權值。
  (2)區間修改。例如圖(1)中把從d到e的路徑上的邊d-b、b-e都減1,此時邊b-a並沒有被影響到;但是在圖(2)中,把d、b、e三個結點的值都減了1,而b點的值是不該被減的。
  觀察到b = LCA(d, e),所以解決方法是不要處理LCA:
  (1)區間[L, R]求和時,不計算LCA(L, R)的值;
  (2)區間[L, R]更新時,不更新LCA(L, R)的值。

 https://blog.csdn.net/weixin_43914593/article/details/109709506


 

#include<iostream>
#include<vector>
#include<queue>
#include<cstring>
#include<cmath>
#include<map>
#include<set>
#include<cstdio>
#include<algorithm>
#define debug(a) cout<<#a<<"="<<a<<endl;
using namespace std;
const int maxn=1e5+1000;
typedef long long LL;
typedef pair<LL,LL>P;
struct Tree{
    LL l,r,sum,tag;
}tree[maxn*4];
LL new_w[maxn];
///線段樹部分
void push_up(LL p){
    tree[p].sum=tree[p*2].sum+tree[p*2+1].sum;
}
void addtag(LL p,LL d){
    tree[p].tag+=d;
    tree[p].sum+=d*(tree[p].r-tree[p].l+1);
}
void push_down(LL p){
    if(tree[p].tag!=0){
        addtag(p*2,tree[p].tag);
        addtag(p*2+1,tree[p].tag);
        tree[p].tag=0;
    }
}
void build(LL p,LL l,LL r){
    tree[p].l=l;tree[p].r=r;tree[p].tag=0;tree[p].sum=0;
    if(l==r){tree[p].sum=new_w[l];return;}
    LL mid=(l+r)>>1;
    build(p*2,l,mid);
    build(p*2+1,mid+1,r);
    push_up(p);
}
void modify(LL p,LL l,LL r,LL d){
    if(l<=tree[p].l&&r>=tree[p].r){
            addtag(p,d);
            return;
    }
    push_down(p);
    LL mid=(tree[p].l+tree[p].r)>>1;
    if(l<=mid) modify(p*2,l,r,d);
    if(r>mid) modify(p*2+1,l,r,d);
    push_up(p);
}
LL query(LL p,LL l,LL r){
    if(l<=tree[p].l&&r>=tree[p].r){
        return tree[p].sum;
    }
    push_down(p);
    LL mid=(tree[p].l+tree[p].r)>>1;
    LL ans=0;
    if(l<=mid) ans+=query(p*2,l,r);
    if(r>mid) ans+=query(p*2+1,l,r);
    return ans;
}
///樹鏈剖分部分
LL siz[maxn],son[maxn],dep[maxn],fa[maxn];
LL top[maxn];
LL id[maxn],tot=0,a[maxn];
vector<P>g[maxn];///first存要到的節點,second存邊權
void predfs(LL u,LL father){
    siz[u]=1;dep[u]=dep[father]+1;
    fa[u]=father;
    for(LL i=0;i<g[u].size();i++){
        LL v=g[u][i].first;
        if(v==father) continue;
        a[v]=g[u][i].second;///點權轉邊權
        predfs(v,u);
        siz[u]+=siz[v];
        if(siz[v]>siz[son[u]]){
            son[u]=v;
        }
    }
}
void dfs(LL u,LL topx){
    top[u]=topx;
    id[u]=++tot;
    new_w[tot]=a[u];
    if(!son[u]) return;///邊界葉子節點
    dfs(son[u],topx);
    for(LL i=0;i<g[u].size();i++){
        LL v=g[u][i].first;
        if(v==fa[u]||v==son[u]) continue;
        dfs(v,v);
    }
}
void modify_path(LL u,LL v,LL d){
    while(top[u]!=top[v]){
        if(dep[top[u]]<dep[top[v]]){
            swap(u,v);
        }
        modify(1,id[top[u]],id[u],1);
        u=fa[top[u]];
    }
    if(dep[u]>dep[v]) swap(u,v);
    modify(1,id[u],id[v],1);
    LL LCA=dep[u]<dep[v]?u:v;
    modify(1,id[LCA],id[LCA],-1);
}
LL query_path(LL u,LL v){
    LL ans=0;
    while(top[u]!=top[v]){
        if(dep[top[u]]<dep[top[v]]){
            swap(u,v);
        }
        ans+=query(1,id[top[u]],id[u]);
        u=fa[top[u]];
    }
    if(dep[u]>dep[v]) swap(u,v);
    ans+=query(1,id[u],id[v]);
    LL LCA=dep[u]<dep[v]?u:v;
    ans-=query(1,id[LCA],id[LCA]);
    return ans;
}
int main(void)
{
  cin.tie(0);std::ios::sync_with_stdio(false);
  LL n,m;cin>>n>>m;
  for(LL i=1;i<n;i++){
    LL u,v;cin>>u>>v;
    g[u].push_back({v,0});
    g[v].push_back({u,0});
  }
  predfs(1,0);
  dfs(1,1);
  build(1,1,n);
  while(m--){
    char op;LL l,r;
    cin>>op>>l>>r;
    if(op=='P'){
        modify_path(l,r,1);
    }
    if(op=='Q'){
        cout<<query_path(l,r)<<endl;
    }
  }
return 0;
}

 

相關文章