1. 程式人生 > >【BZOJ】3052: [wc2013]糖果公園 樹分塊+待修改莫隊算法

【BZOJ】3052: [wc2013]糖果公園 樹分塊+待修改莫隊算法

.html algo 2種 過程 BE oid park wap rev

【題目】#58. 【WC2013】糖果公園

【題意】給定n個點的樹,m種糖果,每個點有糖果ci。給定n個數wi和m個數vi,第i顆糖果第j次品嘗的價值是v(i)*w(j)。q次詢問一條鏈上每個點價值的和或修改一個點的糖果ci。n,m,q<=10^5。

【算法】樹分塊+帶修改莫隊算法

【題解】參考:WC 2013 糖果公園 park 題解 by vfleaking

首先樹分塊,參考王室聯邦的方法。確定塊大小為B,一遍DFS可以分成若幹大小為[B,3B]的塊,性質是塊內兩點距離至多為B。

定義(x,y,t)表示詢問經過了t次修改的樹鏈x-y的答案,將詢問排序:第一關鍵字belong[x],第二關鍵字belong[y],第三關鍵字t。

對於一個詢問,要考慮從上一次詢問(x‘,y‘,t‘)轉移。首先轉移t,只需要記錄每次修改前和修改後的數值,就可以實現修改或逆修改了。

然後是從樹鏈x‘-y‘轉移到樹鏈x-y‘,這裏需要異或操作(對稱差)。所謂異或操作,就是如果x標記過就減去答案並消除標記,如果x沒標記過就加上答案並增加標記。

具體過程可以參考vfk的公式推導,感性理解也很簡單:定義t(x,y)表示除了lca(x,y)的樹鏈x-y,那麽 t(x,y‘) = t(x‘,y‘) ^ t(x,x‘)

有了這個,我們只要在當前基礎上異或一下t(x,x‘)和t(y,y‘)就可以實現從x‘-y‘轉移到x-y了,當然LCA全部另外算就可以了。

另外,之前修改點x的數值時,如果在當前答案中必須消除,修改,再加入。

【復雜度分析】假設塊大小為B。(隨便看看就好了,這部分不保證正確……)

如果u和v都不移出塊,那麽位置移動復雜度O(q*B),時間移動復雜度O(q)。

如果v移出塊,那麽因為belong[u]只有n/B種可能,位置移動復雜度O(n*n/B)。

如果u移出塊,那麽位置移動的復雜度O(n)。

而belong[u],belong[v]只有(n/B)^2種可能,所以時間移動的復雜度是O(q*(n/B)^2)。

平衡後B=n^(2/3)。

所以總復雜度O(n^(5/3))。

因為樹分塊的塊大小實際上比B大,所以取B=N^(2/3)*0.5時常數比較優秀。

技術分享圖片
#include<cmath>
#include<cstdio>
#include<cctype>
#include<cstring>
#include<algorithm>
using namespace std;
int read(){
    int s=0,t=1;char c;
    while(!isdigit(c=getchar()))if(c==-)t=-1;
    do{s=s*10+c-0;}while(isdigit(c=getchar()));
    return s*t;
}
const int maxn=100010;
int tot,n,m,q,B,top,cnt,c0,c1;
int first[maxn],deep[maxn],f[maxn][20],belong[maxn],st[maxn],num[maxn],c[maxn],w[maxn],v[maxn],pre[maxn];
long long ans,ANS[maxn];
bool vis[maxn];
struct edge{int v,from;}e[maxn*2];
struct C0{int x,y,pre;}a[maxn];
struct C1{int x,y,t,id;}b[maxn];
void insert(int u,int v){tot++;e[tot].v=v;e[tot].from=first[u];first[u]=tot;}
void dfs(int x,int fa){
    int lim=top;
    for(int j=1;(1<<j)<=deep[x];j++)f[x][j]=f[f[x][j-1]][j-1];
    for(int i=first[x];i;i=e[i].from)if(e[i].v!=fa){
        deep[e[i].v]=deep[x]+1;
        f[e[i].v][0]=x;
        dfs(e[i].v,x);
        if(top-lim>=B){
            cnt++;
            while(top>lim)belong[st[top--]]=cnt;
        }
    }
    st[++top]=x;
}
int lca(int x,int y){
    if(deep[x]<deep[y])swap(x,y);
    int d=deep[x]-deep[y];
    for(int j=0;(1<<j)<=d;j++)if((1<<j)&d)x=f[x][j];
    if(x==y)return x;
    for(int j=17;j>=0;j--)if((1<<j)<=deep[x]&&f[x][j]!=f[y][j])x=f[x][j],y=f[y][j];
    return f[x][0];
}
void reverse(int x){
    if(vis[x])ans-=1ll*w[num[c[x]]--]*v[c[x]];
    else ans+=1ll*w[++num[c[x]]]*v[c[x]];
    vis[x]^=1;
}
void modify(int x,int y){
    if(!vis[x])c[x]=y;
    else reverse(x),c[x]=y,reverse(x);
}
void solve(int x,int y){
    while(x!=y){
        if(deep[x]>deep[y])reverse(x),x=f[x][0];
        else reverse(y),y=f[y][0];
    }
}
bool cmp(C1 a,C1 b){return belong[a.x]<belong[b.x]||(belong[a.x]==belong[b.x]&&belong[a.y]<belong[b.y])||
(belong[a.x]==belong[b.x]&&belong[a.y]==belong[b.y]&&a.t<b.t);}

int main(){
    n=read();m=read();q=read();
    for(int i=1;i<=m;i++)v[i]=read();
    for(int i=1;i<=n;i++)w[i]=read();
    for(int i=1;i<n;i++){                      
        int u=read(),v=read();
        insert(u,v);insert(v,u);
    }
    B=pow(n,2.0/3)*0.5;
    dfs(1,0);
    while(top)belong[st[top--]]=cnt;
    for(int i=1;i<=n;i++)c[i]=pre[i]=read();
    for(int i=1;i<=q;i++){
        int kind=read(),x=read(),y=read();
        if(!kind){
            a[++c0]=(C0){x,y,pre[x]},pre[x]=y;
        }
        else{
            b[++c1]=(C1){x,y,c0,c1};
        }
    }
    sort(b+1,b+c1+1,cmp);
    for(int i=1;i<=b[1].t;i++)modify(a[i].x,a[i].y);
    solve(b[1].x,b[1].y);
    int c=lca(b[1].x,b[1].y);
    reverse(c);ANS[b[1].id]=ans;reverse(c);
    for(int i=2;i<=c1;i++){
        for(int j=b[i-1].t+1;j<=b[i].t;j++)modify(a[j].x,a[j].y);
        for(int j=b[i-1].t;j>b[i].t;j--)modify(a[j].x,a[j].pre);
        solve(b[i-1].x,b[i].x);solve(b[i-1].y,b[i].y);
        int c=lca(b[i].x,b[i].y);
        reverse(c);ANS[b[i].id]=ans;reverse(c);
    }
    for(int i=1;i<=c1;i++)printf("%lld\n",ANS[i]);
    return 0;
}
View Code

【BZOJ】3052: [wc2013]糖果公園 樹分塊+待修改莫隊算法