1. 程式人生 > >BZOJ3435[Wc2014]紫荊花之戀——動態點分治(替罪羊式點分樹套替罪羊樹)

BZOJ3435[Wc2014]紫荊花之戀——動態點分治(替罪羊式點分樹套替罪羊樹)

play sin \n 子節點 包含 對數 查找 efi tro

題目描述

強強和萌萌是一對好朋友。有一天他們在外面閑逛,突然看到前方有一棵紫荊樹。這已經是紫荊花飛舞的季節了,無數的花瓣以肉眼可見的速度從紫荊樹上長了出來。仔細看看的話,這個大樹實際上是一個帶權樹。每個時刻它會長出一個新的葉子節點。每個節點上有一個可愛的小精靈,新長出的節點上也會同時出現一個新的小精靈。小精靈是很萌但是也很脆弱的生物,每個小精靈 i 都有一個感受能力值Ri ,小精靈 i, j 成為朋友當且僅當在樹上 i 和 j 的距離 dist(i,j) ≤ Ri + R! ,其中 dist(i, j)表示在這個樹上從 i 到 j 的唯一路徑上所有邊的邊權和。強強和萌萌很好奇每次新長出一個葉子節點之後,這個樹上總共有幾對朋友。

我們假定這個樹一開始為空,節點按照加入的順序從 1開始編號。由於強強非常好奇, 你必須在他每次出現新節點後馬上給出總共的朋友對數,不能拖延哦。

輸入

共有 n + 2 行。
第一行包含一個正整數,表示測試點編號。
第二行包含一個正整數 n ,表示總共要加入的節點數。
我們令加入節點前的總共朋友對數是 last_ans,在一開始時它的值為0。
接下來 n 行中第 i 行有三個數 ai, bi, ri,表示節點 i 的父節點的編號為 ai xor (last_ans mod 10^9) (其中xor 表示異或,mod 表示取余,數據保證這樣操作後得到的結果介於 1到i – 1之間),與父節點之間的邊權為 ci,節點 i 上小精靈的感受能力值為r!。

註意 a1 = c1 = 0,表示 1 號點是根節點,對於 i > 1,父節點的編號至少為1。

輸出

包含 n 行,每行輸出1 個整數, 表示加入第 i 個點之後,樹上有幾對朋友。

樣例輸入

0
5
0 0 6
1 2 4
0 9 4
0 5 5
0 2 4

樣例輸出

0
1
2
4
7

提示

1<=Ci<=10000
Ai<=2*10^9
Ri<=10^9
N<=100000

經過一天的卡常+優化,經過從非旋轉treap到旋轉treap到替罪羊樹的改進,終於過掉了這個傳說中的神仙題!

因為這道題比較繁瑣,我們分塊講解。

點分治

按照動態點分治的常規套路,我們考慮對於一棵給定的樹,單次詢問如何用點分治處理。

當以x為分治中心時,如果聯通塊中i,j兩個點滿足條件,假設i,j距離分治中心距離分別為di,dj,那麽di+dj<=ri+rj,即di-ri<=rj-dj。

那麽遍歷分治中心的每個子樹,記錄每個點的ri-di,對於每個點x找到有多少個點的ri-di>=dx-rx,同樣容斥去掉兩點位於同一棵子樹中的答案。

對於維護每個點的ri-di只要開一棵平衡樹即可(因為本題強制在線不能離散化,所以不能使用權值線段樹)。

平衡樹

因為本題用到的平衡樹功能單一,所以建議使用高速平衡樹(除非旋轉treap和splay之外的其他平衡樹)。

當然如果你有高超的卡常技巧也可以用splay或非旋轉treap。

對於高速平衡樹的選擇,個人建議選擇插入時間復雜度較優的SBT或替罪羊樹(原因後面會說)。

對於替罪羊樹,經過不懈嘗試,我發現重構因子為0.86時的時間復雜度比較優秀。

動態點分治&點分樹

多次詢問顯然要用點分樹來解決,通過上面對單次查詢點分治的處理方法,我們可以知道點分樹上每個點需要維護的信息:

1、每個點開一棵平衡樹維護以它為分治中心時聯通塊內所有點的ri-di(di為聯通塊內點為到它的距離)。

2、每個點開一棵平衡樹維護以它為分治中心時聯通塊內所有點的ri-di(di為聯通塊內點到它在點分樹上父節點的距離)。

那麽顯然對於靜態的樹,單次修改和查詢只要從操作點往點分樹的根爬並對沿途點進行修改或統計信息即可。

替罪羊式重構

本題的重點是邊加點邊詢問,那麽問題來了:如何構建外層點分樹?

顯然不能加一個點重構一次點分樹,而點分樹也不具有treap等平衡樹的可旋性,但是我們可以像替罪羊樹一樣重構!

我們知道點分樹要求每個點的子樹中所有點都是以它為分治中心時能遍歷到的點,那麽在新加一個點時我們不妨直接將這個點加到它原樹的父節點下面。

與替罪羊樹相同,我們同樣需要一個重構因子,這裏因為重構時間復雜度較高,所以重構因子建議設成0.9.

在往根方向修改沿途信息時我們記錄距離根最近的一個需要重構的點,然後將這個子樹重構。

因為這棵子樹在原樹上一定是一個聯通塊,所以直接對這個聯通塊進行一遍點分治建出這個聯通塊真正的點分樹即可。

因為需要將聯通塊內所有點的vis標記都清空,所以還需要每個點開一個vector存一下這個點在點分樹上的子樹中都有哪些點。

當重構外層點分樹時,因為點分樹上的父子關系改變,所以內層平衡樹的信息也要發生相應的變化。

因為外層不是平衡樹,我們無法像平衡樹一樣通過上傳來重新改變內層信息,所以只能暴力刪除平衡樹並暴力重新插入信息。

這也是為什麽要用插入時間復雜度較優的平衡樹的原因。

LCA

同樣因為動態加點,無法用RMQ或者樹鏈剖分等方法求兩點在原樹的LCA,對於新加入的每個點,只能用倍增來求LCA。

時間復雜度

本題的時間復雜度主要在外層重構及內層平衡樹的重建上。

對於外層點分樹,重構的時間復雜度每次均攤O(logn)。

對於內層平衡樹的重建,因為每個平衡樹平均logn個節點,每次插入時間復雜度O(logn),所以重建一棵平衡樹時間復雜度O(logn^2)。

一次內層重建logn棵平衡樹時間復雜度是O(logn^3)。

同樣對於一次查詢或修改需要遍歷logn個節點,每次求LCA+平衡樹上查找時間復雜度O(logn^2),單次查詢或修改時間復雜度O(logn^3)。

綜上所述,總時間復雜度O(nlogn^3)。

#include<cmath>
#include<vector>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define ll long long
using namespace std;
inline char _read()
{
    static char buf[100000],*p1=buf,*p2=buf;
    return p1==p2&&(p2=(p1=buf)+fread(buf,1,100000,stdin),p1==p2)?EOF:*p1++;
}
inline int read()
{
    int x=0,f=1;char ch=_read();
    while(ch>‘9‘||ch<‘0‘){if(ch==‘-‘)f=-1;ch=_read();}
    while(ch>=‘0‘&&ch<=‘9‘){x=(x<<3)+(x<<1)+ch-‘0‘;ch=_read();}
    return x*f;
}
int n;
ll ans;
int rot;
int num;
int tot;
int cnt;
int top;
int type;
int point;
int x,y,z;
int a,b,c;
int r[100010];
int d[100010];
int f[100010];
int lg[100010];
int to[200010];
int mx[100010];
int dep[100010];
int vis[100010];
int root[100010];
int size[100010];
int head[100010];
int next[200010];
int st[4000010];
int froot[100010];  
int g[100010][18];
vector<int>q[100010];
const int mod=1000000000;
struct balance_tree
{
    int cot;
    int *flag;
    int q[4000010];
    int v[4000010];
    int ls[4000010];
    int rs[4000010];
    int sum[4000010];
    inline bool bad(int rt)
    {
        if(sum[rt]*86<=100*max(sum[ls[rt]],sum[rs[rt]]))
        {
            return true;
        }
        return false;
    }
    inline void dfs(int rt)
    {
        if(!rt)
        {
            return ;
        }
        dfs(ls[rt]);
        q[++cot]=rt;
        dfs(rs[rt]);
    }
    inline void build(int &rt,int l,int r)
    {
        int mid=(l+r)>>1;
        rt=q[mid];
        if(l==r)
        {
            ls[rt]=rs[rt]=0;
            sum[rt]=1;
            return ;
        }
        if(l<mid)
        {
            build(ls[rt],l,mid-1);
        }
        else
        {
            ls[rt]=0;
        }
        build(rs[rt],mid+1,r);
        sum[rt]=sum[ls[rt]]+sum[rs[rt]]+1;
    }
    inline void rebuild(int &rt)
    {
        cot=0;
        dfs(rt);
        if(cot)
        {
            build(rt,1,cot);
        }
        else
        {
            rt=0;
        }
    }
    inline void insert(int &rt,int k)
    {
        if(!rt)
        {
            if(top)
            {
                rt=st[top--];
            }
            else
            {
                rt=++cnt;
            }
            v[rt]=k;
            sum[rt]=1;
            return ;
        }
        sum[rt]++;
        if(v[rt]>=k)
        {
            insert(ls[rt],k);
        }
        else
        {
            insert(rs[rt],k);
        }
        if(bad(rt))
        {
            flag=&rt;
        }
    }
    inline void ins(int &rt,int val)
    {
        flag=0;
        insert(rt,val);
        if(flag)
        {
            rebuild(*flag);
        }
    }
    inline void del(int &rt)
    {
        if(!rt)
        {
            return ;
        }
        del(ls[rt]);
        del(rs[rt]);
        v[rt]=sum[rt]=0;
        st[++top]=rt;
        rt=0;
    }
    inline int query(int root,int k)
    {
        int rt=root;
        int ans=0;
        while(rt)
        {
            if(v[rt]>=k)
            {
                rt=ls[rt];
            }
            else
            {
                ans+=sum[ls[rt]]+1;
                rt=rs[rt];
            }
        }
        return sum[root]-ans;
    }
}tr;
inline void add(int x,int y)
{
    next[++tot]=head[x];
    head[x]=tot;
    to[tot]=y;
}
inline int lca(int x,int y)
{
    if(d[x]<d[y])
    {
        swap(x,y);
    }
    int deep=d[x]-d[y];
    for(int i=0;i<=lg[deep];i++)
    {
        if((deep&(1<<i)))
        {
            x=g[x][i];
        }
    }
    if(x==y)
    {
        return x;
    }
    for(int i=lg[d[x]];i>=0;i--)
    {
        if(g[x][i]!=g[y][i])
        {
            x=g[x][i];
            y=g[y][i];
        }
    }
    return g[x][0];
}
inline int dis(int x,int y)
{
    return dep[x]+dep[y]-(dep[lca(x,y)]<<1);
}
inline void getroot(int x,int fa)
{
    size[x]=1;
    mx[x]=0;
    for(int i=head[x];i;i=next[i])
    {
        if(!vis[to[i]]&&to[i]!=fa)
        {
            getroot(to[i],x);
            size[x]+=size[to[i]];
            mx[x]=max(mx[x],size[to[i]]);
        }
    }
    mx[x]=max(mx[x],num-size[x]);
    if(mx[x]<mx[rot])
    {
        rot=x;
    }
}
inline void dfs(int x,int fa,int rt)
{
    size[x]=1;
    q[rt].push_back(x);
    tr.ins(root[rt],r[x]-dis(x,rt));
    if(f[rt])
    {
        tr.ins(froot[rt],r[x]-dis(x,f[rt]));
    }
    for(int i=head[x];i;i=next[i])
    {
        if(!vis[to[i]]&&to[i]!=fa)
        {
            dfs(to[i],x,rt);
            size[x]+=size[to[i]];
        }
    }
}
inline void partation(int x,int fa)
{
    tr.del(root[x]);
    tr.del(froot[x]);
    q[x].clear();
    f[x]=fa;
    vis[x]=1;
    dfs(x,0,x);
    for(int i=head[x];i;i=next[i])
    {
        if(!vis[to[i]])
        {
            num=size[to[i]];
            rot=0;
            getroot(to[i],0);
            partation(rot,x);
        }
    }
}
inline void insert(int x)
{
    point=-1;
    for(int i=x;i;i=f[i])
    {
        q[i].push_back(x);
        tr.ins(root[i],r[x]-dis(x,i));
        if(f[i])
        {
            tr.ins(froot[i],r[x]-dis(x,f[i]));
        }
        size[i]++;
        if(f[i]&&size[i]*100>(size[f[i]]+1)*90)
        {
            point=f[i];
        }
    }
    if(point!=-1)
    {
        int len=q[point].size();
        for(int i=0;i<len;i++)
        {
            vis[q[point][i]]=0;
        }
        num=size[point];
        rot=0;
        getroot(point,0);
        partation(rot,f[point]);
    }
}
inline int query(int x)
{
    int res=0;
    for(int i=x;i;i=f[i])
    {
        res+=tr.query(root[i],dis(x,i)-r[x]);
    }
    for(int i=x;f[i];i=f[i])
    {
        res-=tr.query(froot[i],dis(x,f[i])-r[x]);
    }
    return res;
}
int main()
{
    type=read();n=read();
    x=read();y=read();z=read();
    printf("0\n");
    r[1]=z;
    tr.ins(root[1],r[1]);
    q[1].push_back(1);
    mx[0]=1<<30;
    size[1]=vis[1]=1;
    for(int i=2;i<=n;i++)
    {
        lg[i]=lg[i>>1]+1;
        x=read();y=read();z=read();
        x^=(ans%mod);
        r[i]=z;
        add(i,x);
        add(x,i);
        g[i][0]=f[i]=x;
        d[i]=d[x]+1;
        dep[i]=dep[x]+y;
        vis[x]=1;
        for(int j=1;(1<<j)<=d[i];j++)
        {
            g[i][j]=g[g[i][j-1]][j-1];
        }
        ans+=query(i);
        printf("%lld\n",ans);
        insert(i);
    }
}

BZOJ3435[Wc2014]紫荊花之戀——動態點分治(替罪羊式點分樹套替罪羊樹)