bzoj3730. 震波

动态点分治

考虑从每一个"块"里找到距离k范围内的点的和

为了去重,

每个x维护两个线段树:(都是关于自己分治树子树的点)

1.下标为距离x的距离,权值为val的

2.下标为距离x的分治树father的距离,权值为val

这样,统计的时候

计算分治树祖先块的时候,把从自己那里出来的块的东西再减去

注意:

1.点分治nowrt是全局变量,所以divi这一层用tmp额外记录一下,否则回溯回来再下去的时候,nowrt就不是这一层的了

2.不能某个祖先的dis大于k,就直接break了。因为到分治树祖先的距离没有单调性!!!!

代码:

#include<bits/stdc++.h>
#define il inline
#define reg register int
#define numb (ch^'0')
#define mid ((l+r)>>1)
using namespace std;
typedef long long ll;
il void rd(int &x){
    char ch;bool fl=false;
    while(!isdigit(ch=getchar()))(ch=='-')&&(fl=true);
    for(x=numb;isdigit(ch=getchar());x=x*10+numb);
    (fl==true)&&(x=-x);
}
namespace Miracle{
const int N=100000+5;
int n,m;
struct node{
    int nxt,to;
}e[2*N];
int hd[N],cnt;
void add(int x,int y){
    e[++cnt].nxt=hd[x];
    e[cnt].to=y;
    hd[x]=cnt;
}
int fa[N];
int sz[N];
int mx[N];
int nowsz;
int tmprt;
int rt[N][2];//0 is myself ; 1 is disfather
int nowrt;
struct tr{
    int ls,rs;
    int sum;
}t[N*50];
int tot;
int dist[N][20];
int dep[N];
int val[N];
int vis[N];
void pushup(int x){
    t[x].sum=t[t[x].ls].sum+t[t[x].rs].sum;
}
void upda(int &x,int l,int r,int p,int v){
    if(!x) x=++tot;
    if(l==r){
        t[x].sum+=v;
        return;
    }
    if(p<=mid) upda(t[x].ls,l,mid,p,v);
    else upda(t[x].rs,mid+1,r,p,v);
    pushup(x);
}
int query(int x,int l,int r,int L,int R){//no dis=0 (myself)
    if(L>R) return 0;
    if(L<=l&&r<=R){
        return t[x].sum;
    }
    int ret=0;
    if(L<=mid) ret+=query(t[x].ls,l,mid,L,R);
    if(mid<R) ret+=query(t[x].rs,mid+1,r,L,R);
    return ret;
}
void dfs1(int x,int f,int d,int dis){//find nowrt
    sz[x]=1;
    mx[x]=0;
    dep[x]=d;
    if(d!=1){
        upda(tmprt,0,n,dis,val[x]);
    }
    for(reg i=hd[x];i;i=e[i].nxt){
        int y=e[i].to;
        if(vis[y]||y==f) continue;
        dfs1(y,x,d,dis+1);
        sz[x]+=sz[y];
        mx[x]=max(mx[x],sz[y]);
    }
    mx[x]=max(mx[x],nowsz-sz[x]);
    if(mx[x]<=nowsz/2&&(nowsz-sz[x])<=nowsz/2){
        nowrt=x;
    }
}
void dfs2(int x,int f,int d,int dis){//find dis from nowrt
    sz[x]=1;
    dep[x]=d;
    dist[x][d]=dis;
    upda(rt[nowrt][0],0,n,dis,val[x]);
    for(reg i=hd[x];i;i=e[i].nxt){
        int y=e[i].to;
        if(vis[y]||y==f) continue;
        dfs2(y,x,d,dis+1);
        sz[x]+=sz[y];
    }
}
void divi(int x,int d,int f){
    //mxsz=0;
//    cout<<" x "<<x<<" : "<<d<<" "<<f<<endl;
    nowrt=0;
    tmprt=0;
    dfs1(x,0,d,1);
    fa[nowrt]=f;
    rt[nowrt][1]=tmprt;
//    cout<<" nowrt "<<nowrt<<endl;
    dfs2(nowrt,0,d,0);
    
    vis[nowrt]=1;
    int tmp=nowrt;
    for(reg i=hd[tmp];i;i=e[i].nxt){
        int y=e[i].to;
        if(!vis[y]){
            nowsz=sz[y];
            divi(y,d+1,tmp);
        }
    }
}
int wrk0(int x,int k){
    int ret=0;
    ret+=query(rt[x][0],0,n,0,k);
    int tmp=x;
    while(fa[x]){
        int y=fa[x];
    //    if(k-dist[tmp][dep[y]]>=0) 
        ret+=query(rt[y][0],0,n,0,k-dist[tmp][dep[y]]);
    //    else break;
        ret-=query(rt[x][1],0,n,0,k-dist[tmp][dep[y]]);
        
        x=y;
    }
    return ret;
}
void wrk1(int x,int v){
    int tmp=x;
    int c=v-val[x];
    val[x]=v;
    upda(rt[x][0],0,n,0,c);
    while(fa[x]){
        int y=fa[x];
        upda(rt[x][1],0,n,dist[tmp][dep[y]],c);
        upda(rt[y][0],0,n,dist[tmp][dep[y]],c);
        x=y;
    }
}
int main(){
    rd(n);rd(m);
    for(reg i=1;i<=n;++i) rd(val[i]);
    int x,y;
    for(reg i=1;i<n;++i){
        rd(x);rd(y);
        add(x,y);add(y,x);
    }
    nowsz=n;
    divi(1,1,0);
//    cout<<" toot "<<tot<<endl;
    int op,k;
    int lasans=0;
    while(m--){
        rd(op);
        rd(x);rd(k);
        x^=lasans;k^=lasans;
        if(op==0) printf("%d
",lasans=wrk0(x,k));
        else wrk1(x,k);
    }
    return 0;
}
 
}
signed main(){
//    freopen("data.in","r",stdin);
//    freopen("my.out","w",stdout);
    Miracle::main();
    return 0;
}

/*
   Author: *Miracle*
   Date: 2019/2/6 10:11:29
*/

总结:

动态点分治往往为了去重,经常考虑x对fa的贡献

相当于在分治树的往fa走的边上造了一个线段树

(每个点一个,每个边一个,一共n+n-1个)

原文地址:https://www.cnblogs.com/Miracevin/p/10353703.html