P3384 [Template] Divisão de cadeia leve e pesada (divisão de cadeia de árvore para construir placa de manutenção de árvore de segmento de linha)

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


   Conteúdo refere-se ao blog do professor Luo     

   A cadeia pesada tem características importantes: a sequência DFS dos nós internos de uma cadeia pesada é contínua . Este recurso torna possível usar uma estrutura de dados (geralmente uma árvore de segmento de linha) para manter a cadeia pesada, resolvendo assim de forma eficiente alguns problemas de árvore, como os seguintes problemas:
   (1) Modifique o peso de cada ponto no caminho do ponto x ao ponto y .
   (2) Consulte a soma dos pesos dos nós no caminho do ponto x ao ponto y.
   (3) Modifique o peso de cada ponto no ponto x subárvore.
   (4) Consulte a soma dos pesos de todos os nós na subárvore do ponto x.
   Entre eles, (1) está o problema da "diferença na árvore". A diferença na árvore só pode resolver o problema da modificação simples. Para o problema de modificar a subárvore inteira como (3), a diferença na árvore não funcionará.


1. A sequência DFS da cadeia pesada A
   função dfs2 () fornecida anteriormente é que DFS é o filho mais pesado primeiro, e então DFS é o filho mais pesado. Se você usar o número id [x] para registrar a sequência DFS do nó x na primeira frase de dfs2 ():
      id [x] = ++ num;
   O resultado da renumeração de cada nó, por exemplo, a seguinte figura:

 É fácil observar:
   (1) Os números dos nós internos de cada cadeia pesada são ordenados . A ordem DFS da cadeia pesada {a, b, e, j, q} é {1, 2, 3, 4, 5}; a ordem DFS da cadeia pesada {d, p} é {7, 8}; a cadeia pesada {c A ordem DFS de, f} é {10, 11}.
   (2) A ordem DFS de todos os nós em cada subárvore também é contínua . Por exemplo, para subárvores {e, i, j, q} com raiz em e, sua ordem DFS é {3, 4, 5, 6}.
   Aqui está o conteúdo principal: Use árvores de segmento de linha para lidar com correntes pesadas. Visto que os nós dentro de cada cadeia pesada são ordenados, eles podem ser organizados em uma árvore de segmento de linha na ordem DFS. Em relação a cada cadeia pesada como um intervalo contínuo , a modificação e consulta de uma cadeia pesada são tratadas por uma árvore de segmento de linha; se o caminho de x para y cruzar várias cadeias pesadas, simplesmente pule-o.
   Resumindo: "A árvore do segmento de linha é usada dentro da cadeia pesada e a cadeia pesada é ignorada ."

 

A figura acima reorganiza os nós da cadeia de árvore em uma árvore de segmento de linha. Os nós da mesma cadeia pesada são contínuos na árvore do segmento de linha.

2. Modifique o caminho mais curto dos pesos dos nós
  xey no caminho mais curto do nó x para y por meio de LCA (x, y). Este é, na verdade, um processo de encontrar LCA (x, y). Use a corrente pesada para modificar os pesos dos nós no caminho:
  (1) Faça a profundidade da cabeça da corrente de x mais profunda, ou seja, topo [x] ≥ topo [y]. Começando de x e subindo, primeiro suba ao longo da cadeia pesada onde x está localizado e modifique os nós desta seção;
  (2) Depois de atingir a cabeça da cadeia de x, pule um lado leve e alcance a cadeia pesada anterior;
  ( 3) Continue a executar (1) e (2) até que xey estejam na mesma cadeia pesada e, a seguir, modifique o peso do nó entre os dois pontos neste momento. Fim.
  Por exemplo, modifique a soma dos pesos de todos os nós no caminho de p para q:
  (1) Vá de p para o topo de sua cadeia [p] = d, modifique os pesos de pe d;
  (2) pule para b;
  (3) b e q estão na mesma cadeia pesada, modifique o peso de b para q; fim.
  Use a árvore de segmento de linha para lidar com o processo acima, e ainda tome a modificação da soma dos nós no caminho de p para q como um exemplo:
  (1) Salte de p para o cabeçalho da cadeia d, p e d pertencem à mesma cadeia pesada, use a árvore de segmento de linha para modificar o correspondente [7, 8] Intervalo;
  (2) Cruze o lado leve (b, d) de d para a cadeia pesada onde b está localizado;
  (3) Marque b para q, eles pertencem à mesma cadeia pesada, use a árvore de segmento de linha para modificar o intervalo correspondente [2, 5], o fim.

3. Consulte a soma dos pesos de todos os nós no caminho de x a y.
  O processo de consulta e modificação é quase o mesmo. Veja a consulta da soma dos nós no caminho de p a q como um exemplo:
  (1) Pule de p para As cabeças de cadeia d, p e d pertencem à mesma cadeia pesada, use a árvore de segmento de linha para consultar o intervalo correspondente [7, 8];
  (2) Cruze o lado leve (b, d) de d para a cadeia pesada onde b está localizado;
  (3) ) Marque b a q, eles pertencem à mesma cadeia pesada, use a árvore de segmento de linha para consultar o intervalo correspondente [2, 5] e final.

4. Modifique o peso de cada ponto na subárvore do nó x, e consulte a soma do peso do nó na subárvore do nó x.
  A ordem DFS de todos os nós em cada subárvore é contínua, ou seja, a cada Uma subárvore corresponde a um intervalo contínuo. Então, modificar e consultar a subárvore é exatamente o mesmo que modificar e consultar a operação da árvore de segmento para o intervalo.


Blog de referência: https://blog.csdn.net/weixin_43914593/article/details/109709506


Idéia: Após a divisão da cadeia da árvore, atribua o valor original ao novo número e use o novo número para construir a árvore do segmento de linha.

#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+2000;
typedef long long LL;
LL n,m,r,mod;
LL siz[maxn],fa[maxn],son[maxn],dep[maxn],top[maxn];
LL id[maxn],tot=0;
LL a[maxn],a_new[maxn];
vector<LL>g[maxn];
///------------------线段树
struct Tree{
    LL l,r,sum,tag;
}tree[maxn*4];
void push_up(LL p){
    tree[p].sum=tree[p*2].sum+tree[p*2+1].sum;
    tree[p].sum%=mod;
}
void addtag(LL p,LL d){
    tree[p].tag+=d;
    tree[p].sum=(tree[p].sum%mod+d*(tree[p].r-tree[p].l+1)%mod)%mod;
}
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].sum=0;tree[p].tag=0;
    if(l==r) {tree[p].sum=a_new[l]%mod;tree[p].tag=0;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%=mod;
    }
    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;
}
///------------------树链剖分

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];
        if(v==father) continue;
        predfs(v,u);
        siz[u]+=siz[v];
        if(siz[v]>siz[son[u]]){
            son[u]=v;
        }
    }
}
void dfs(LL u,LL topx)
{
    id[u]=++tot;
    a_new[tot]=a[u];///注意赋新值
    top[u]=topx;
    if(!son[u]) return;///叶子节点
    dfs(son[u],topx);///先处理重儿子
    for(LL i=0;i<g[u].size();i++){///处理轻儿子
        LL v=g[u][i];
        if(v==fa[u]||v==son[u]) continue;
        dfs(v,v);
    }
}
void changeLCArange(LL u,LL v,LL z){
    while(top[u]!=top[v]){
        if(dep[top[u]]<dep[top[v]]){
            swap(u,v);
        }
        modify(1,id[top[u]],id[u],z);
        u=fa[top[u]];
    }
    if(dep[u]>dep[v]) swap(u,v);
    modify(1,id[u],id[v],z);
}
LL queryLCAdis(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]);
        ans%=mod;
        u=fa[top[u]];
    }
    if(dep[u]>dep[v]) swap(u,v);
    ans+=query(1,id[u],id[v]);
    return ans%=mod;
}
void changeShu(LL x,LL z){
    modify(1,id[x],id[x]+siz[x]-1,z);
}
LL queryShu(LL x){
    return query(1,id[x],id[x]+siz[x]-1)%mod;
}
int main(void)
{
  cin.tie(0);std::ios::sync_with_stdio(false);
  cin>>n>>m>>r>>mod;
  for(LL i=1;i<=n;i++){
    cin>>a[i];
  }
  for(LL i=1;i<n;i++){
    LL u,v;cin>>u>>v;
    g[u].push_back(v);
    g[v].push_back(u);
  }
  predfs(r,0);
  dfs(r,r);
  build(1,1,n);
  for(LL i=1;i<=m;i++){
    LL op;cin>>op;
    if(op==1){
        LL x,y,z;cin>>x>>y>>z;
        changeLCArange(x,y,z);
    }
    if(op==2){
        LL x,y;cin>>x>>y;
        cout<<queryLCAdis(x,y)<<endl;
    }
    if(op==3){
        LL x,z;cin>>x>>z;
        changeShu(x,z);
    }
    if(op==4){
        LL x;cin>>x;
        cout<<queryShu(x)<<endl;
    }
  }
return 0;
}

 

Acho que você gosta

Origin blog.csdn.net/zstuyyyyccccbbbb/article/details/109860444
Recomendado
Clasificación