1. 程式人生 > >BZOJ3123:[SDOI2013]森林

BZOJ3123:[SDOI2013]森林

淺談主席樹:https://www.cnblogs.com/AKMer/p/9956734.html

題目傳送門:https://www.lydsy.com/JudgeOnline/problem.php?id=3123

如果是一棵樹,維護樹上路徑第\(k\)大,我們令\(rt[i]\)為加入\(i\)號結點之後主席樹的根,若我們在\(rt[fa[i]]\)的基礎上建\(rt[i]\)這棵樹,那麼從每個結點的\(rt[i]\)開始,即可訪問原樹的根到自己這一條路徑上所有權值的\(cnt\),那麼\(cnt[u]+cnt[v]-cnt[lca]-cnt[fa[lca]]\)就是路徑上在該權值區間內的結點個數。由於題目保證\(u,v\)

聯通並且路徑上的點大於等於\(k\),所以詢問就迎刃而解了。

我們考慮對於合併,如果運用啟發式合併的思想,每次將大小比較小的樹接在大的樹上,然後重構小的樹,每個點最多會被這樣操作\(logn\)次,每次需要更新主席樹上對應的根和倍增陣列,是\(logn\)複雜度的,所以最後就是\(log^2n\)的。因為每個點最多會被建\(logn\)次,每次會建\(logn\)個節點,所以主席樹大小也要開到\(log^2n\)去。

時間複雜度:\(O(nlog^2n)\)

空間複雜度:\(O(nlog^2n)\)

程式碼如下:

#include <cmath>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;

const int maxn=8e4+5;

int tmp[maxn],a[maxn],rt[maxn];
int n,m,q,cnt,tot,lstans,testcase;
int now[maxn],pre[maxn*2],son[maxn*2];
int dep[maxn],belong[maxn],siz[maxn],f[maxn][18];

int read() {
    int x=0,f=1;char ch=getchar();
    for(;ch<'0'||ch>'9';ch=getchar())if(ch=='-')f=-1;
    for(;ch>='0'&&ch<='9';ch=getchar())x=x*10+ch-'0';
    return x*f;
}

struct tree_node {
    int cnt,ls,rs;
};

struct chairman_tree {
    int tot;
    tree_node tree[maxn*17*17];

    void updata(int p) {
        tree[p].cnt=tree[tree[p].ls].cnt+tree[tree[p].rs].cnt;
    }

    void ins(int lst,int &now,int l,int r,int pos) {
        now=++tot;tree[now]=tree[lst];
        if(l==r) {tree[now].cnt++;return;}
        int mid=(l+r)>>1;
        if(pos<=mid)ins(tree[lst].ls,tree[now].ls,l,mid,pos);
        else ins(tree[lst].rs,tree[now].rs,mid+1,r,pos);
        updata(now);
    }

    int query(int fa1,int fa2,int u,int v,int l,int r,int rk) {
        if(l==r)return tmp[l];
        int mid=(l+r)>>1;
        int sum=tree[tree[u].ls].cnt+tree[tree[v].ls].cnt;
        sum-=(tree[tree[fa1].ls].cnt+tree[tree[fa2].ls].cnt);//sum就是路徑上值在[l,mid]的節點的個數
        if(sum>=rk)return query(tree[fa1].ls,tree[fa2].ls,tree[u].ls,tree[v].ls,l,mid,rk);
        return query(tree[fa1].rs,tree[fa2].rs,tree[u].rs,tree[v].rs,mid+1,r,rk-sum);
    }
}T;

void add(int a,int b) {
    pre[++tot]=now[a];
    now[a]=tot;son[tot]=b;
}

void dfs(int fa,int u,int id) {
    siz[id]++,belong[u]=id;
    f[u][0]=fa,dep[u]=dep[fa]+1;
    for(int i=1;i<=17;i++)
        f[u][i]=f[f[u][i-1]][i-1];
    T.ins(rt[fa],rt[u],1,cnt,a[u]);//每個點建主席樹都在父親主席樹基礎上建
    for(int p=now[u],v=son[p];p;p=pre[p],v=son[p])
        if(v!=fa)dfs(u,v,id);
}

int lca(int u,int v) {
    if(dep[u]<dep[v])swap(u,v);
    for(int i=17;~i;i--)
        if(dep[f[u][i]]>=dep[v])
            u=f[u][i];
    if(u==v)return u;
    for(int i=17;~i;i--)
        if(f[u][i]!=f[v][i])
            u=f[u][i],v=f[v][i];
    return f[u][0];
}

int main() {
    testcase=read();
    n=read();m=read();q=read();
    for(int i=1;i<=n;i++)
        tmp[i]=a[i]=read();
    sort(tmp+1,tmp+n+1);
    cnt=unique(tmp+1,tmp+n+1)-tmp-1;
    for(int i=1;i<=n;i++)
        a[i]=lower_bound(tmp+1,tmp+cnt+1,a[i])-tmp;
    for(int i=1;i<=m;i++) {
        int x=read(),y=read();
        add(x,y);add(y,x);
    }
    for(int i=1;i<=n;i++)
        if(!dep[i])dfs(0,i,i);
    for(int i=1;i<=q;i++) {
        char s[5];scanf("%s",s+1);
        int u=read()^lstans,v=read()^lstans;
        if(s[1]=='Q') {
            int k=read()^lstans;
            int fa=lca(u,v);
            lstans=T.query(rt[f[fa][0]],rt[fa],rt[u],rt[v],1,cnt,k);
            printf("%d\n",lstans);
        }
        else {
            int x=belong[u],y=belong[v];
            if(siz[x]>siz[y])swap(x,y),swap(u,v);
            add(u,v),add(v,u),dfs(v,u,y);//把小的往大的上合併
        }
    }
    return 0;
}