1. 程式人生 > >BZOJ1095 & 動態點分治(好像應該叫點分樹?)學習筆記

BZOJ1095 & 動態點分治(好像應該叫點分樹?)學習筆記

首先要說的是,QTREE4是從這題加強來的,這題可以用括號序列(現在還不會以後學)。

啊既然是學習筆記我來口胡一發。

覺得有這麼一句話說的很好(好像是fjzzq說的),樹上的動態點分治就相當於序列上的線段樹,仔細一想還真有點這意思。

那首先得有個像線段樹一樣的結構對吧,這個結構就是用每次分治的重心串起來的,得到一顆分治樹,分治樹的深度大概在log級別.

然後像線段樹一樣,某個節點變化,只會導致它分治樹上的log個父親的資訊變化,然後對於每個父親節點我們用一些資料結構來維護,使得修改的複雜度大概在log(反正比暴力快嘛),然後修改一個點的複雜度就在log^2了。

學習fjzzq,以下的“這坨樹”表示以某個節點為重心時所控制到的部分樹(當然因為重心不止一個,這個結構也不固定,意思懂就ok啦)

對於這個題。我們是要從重心(下稱為G),兒子中的兩顆不同的子樹裡分別選一條路徑出來。所以對於每個點維護這坨樹中到它分治樹上父親節點的路徑長度並扔進一個set。關於找樹上兩點間路徑長度,我一開始想的是在分治樹上爬到他們的lca然後怎麼算一下,發現我是個煞筆,樹上兩點間路徑長度是個經典問題啊。(後來fstqwq告訴我他幹了很久用分治樹求路徑長度的事情。順便也告訴我現在他用的是RMQ來做這個問題。)放進堆裡。每個點兩個堆。這個叫A堆。

幸好之前學習了很多LCA與RMQ的基情(霧)

然後我們維護下一個,對於每個點,找它分治樹上的兩個兒子,把他們的A堆的堆頂(也就是兒子那坨樹裡最長的路徑)拿出來湊一下就成了(一對x)一條最長的路徑。為了方便後面的操作,這個也用個堆。B堆。

然後預處理部分就搞完了。

修改一個點u的時候,一層層往上爬修改它父親節點。
以下以開一個房間的燈為例。
首先從父親節點fa的B堆把當前節點的堆頂刪除(相當於取消貢獻),然後在當前節點的A堆裡把(u->fa)這條路徑刪除,然後再去用A堆的top更新fa的B堆。本質上是一個重新計算貢獻重新up的過程。

堆的刪除可以用雙堆打標記的方法。暴力一點可以直接上multiset

關燈也是類似噠。然後動態點分治就學完了。qwq不過蒟蒻不敢確定這是動態點分治還是點分樹。(別人也沒有給明確的定義)。

不過總算學完了嘛(笑)。這下可以做SCOI2017d1t2了(然而一個星期過去了我還沒做)。

聽起來就知道程式碼比較長對吧。

//QWsin
#include<queue>
#include<cmath>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
const int logn=18;
const int maxn=100000+10;
const int maxm=200000+10;

int first[maxn],next[maxm],ecnt;
struct Edge{int u,v;Edge(int u=0,int v=0):u(u),v(v){}}e[maxm];
inline void add_edge(int u,int v){
    next[ecnt]=first[u];first[u]=ecnt;e[ecnt++]=Edge(u,v);
    next[ecnt]=first[v];first[v]=ecnt;e[ecnt++]=Edge(v,u);
}

int anc[maxn][logn+2],dep[maxn];
inline void dfs_anc(int u,int pre)
{
    anc[u][0]=pre;dep[u]=dep[pre]+1;
    for(int i=1;(1<<i)+1<=dep[u];++i) anc[u][i]=anc[anc[u][i-1]][i-1];
    for(int i=first[u];i!=-1;i=next[i])
        if(e[i].v!=pre) dfs_anc(e[i].v,u);
}

int n;

inline void init_data()
{
    cin>>n;memset(first,-1,sizeof first);
    for(int i=1,u,v;i<n;++i){
        scanf("%d%d",&u,&v);
        add_edge(u,v);
    }
}

int top,path[maxn],sz[maxn],Msz[maxn],done[maxn];

void DP(int u,int pre)
{
    path[++top]=u;
    sz[u]=1;Msz[u]=0;
    for(int i=first[u];i!=-1;i=next[i])
    {
        int v=e[i].v;   
        if(done[v]||v==pre) continue;
        DP(v,u);
        sz[u]+=sz[v];Msz[u]=max(Msz[u],sz[v]);
    }
}

inline int find_G(int u)
{
    top=0;
    DP(u,u);
    int m=Msz[u],pos=u;
    for(int i=2;i<=top;++i)
    {
        int t=max(Msz[path[i]],sz[u]-sz[path[i]]);
        if(t <= m) {m=t;pos=path[i];}   
    }
    return pos;
}

inline int dis(int a,int b)
{
    int x=a,y=b;
    if(dep[a] > dep[b]) swap(a,b);
    if(dep[a] < dep[b]){
        int t=dep[b]-dep[a];
        for(int i=logn;i>=0;--i)
            if(t-(1<<i) >= 0){
                t-=1<<i;b=anc[b][i];
            }
    }
    if(a!=b) 
    {
        for(int i=logn;i>=0;--i)
            if(anc[a][i]!=anc[b][i])
                a=anc[a][i],b=anc[b][i];
        a=anc[a][0];b=anc[b][0];
    }
    return dep[x]+dep[y]-2*dep[a];
}

struct PQ{
    priority_queue<int>q1,q2;
    inline void push(const int &x){q1.push(x);}
    inline void erase(const int &x){q2.push(x);}
    inline void pop(){
        while(q2.size() && q1.top()==q2.top()) q1.pop(),q2.pop();
        q1.pop();
    }
    inline int top(){
        while(q2.size()&&(q1.top()==q2.top())) q1.pop(),q2.pop();
        return q1.top();
    }
    inline int top2(){
        int t=top();pop();
        int ret=top();push(t);
        return ret;
    }
    inline int size(){return q1.size()-q2.size();}
}f[maxn],g[maxn],ans;//f[u]記到G的距離,g[u]記分治樹上兒子的f[u]堆頂 

inline void work(int u,int pre,const int &G,const int &fa)
{
    f[G].push(dis(u,fa));
    for(int i=first[u];i!=-1;i=next[i])
    {
        int v=e[i].v; 
        if(done[v]||v==pre) continue;   
        work(v,u,G,fa);
    }
}

inline void insert(PQ &q){
    if(q.size() >= 2)   ans.push(q.top()+q.top2());
}

inline void erase(PQ &q){
    if(q.size() >= 2)   ans.erase(q.top()+q.top2());
}

int p[maxn];//分治樹上父親 
inline int DivTree(int u,int fa)
{
    if(u==2){
        int stop=1; 
    }

    int G=find_G(u);
    g[G].push(0);//一切以G為中心!! 
    work(G,G,G,fa);
    done[G]=1;
    for(int i=first[G];i!=-1;i=next[i])
    {
        int v=e[i].v;
        if(done[v]) continue;
        int Gy=DivTree(v,G);
        p[Gy]=G;

//      cout<<f[v].size();

        g[G].push(f[Gy].top());//注意構建的時候要用Gy不能用v,因為我Gy才是分治樹上G的兒子 
    }
    insert(g[G]);
    return G;
}

inline void del(int u)
{
    erase(g[u]);
    g[u].erase(0);
    insert(g[u]);

    for(int t=u;p[t];t=p[t])
    {
        erase(g[p[t]]);
//      cout<<"*"<<g[p[t]].size()<<endl;
        g[p[t]].erase(f[t].top());
        f[t].erase(dis(u,p[t]));
        if(f[t].size()) g[p[t]].push(f[t].top());
        insert(g[p[t]]);
    }
}
inline void add(int u)
{
    erase(g[u]);
    g[u].push(0);
    insert(g[u]);

    for(int t=u;p[t];t=p[t])
    {
        erase(g[p[t]]);
        if(f[t].size()) g[p[t]].erase(f[t].top());
        f[t].push(dis(u,p[t]));
        g[p[t]].push(f[t].top());
        insert(g[p[t]]);
    }
}

int col[maxn];
inline void solve()
{
    int m,cntw=n;cin>>m;
    char op[5];int a;

    while(m--)
    {
        scanf("%s",op);
        if(op[0]=='G') 
        {
            if(cntw<=1) printf("%d\n",cntw-1); 
            else printf("%d\n",ans.top());
        }
        else{
            //反色並update 
            scanf("%d",&a);
            if(!col[a]) {--cntw;del(a);}
            else{++cntw;add(a);}
            col[a]^=1;
        }
    }
}

int main()
{
    init_data();
    dfs_anc(1,1);
    DivTree(1,0);
    solve();

    return 0;
}