【BZOJ3123】[SDOI2013] 森林(啓發式合併主席樹)

點此看題面

大致題意: 給你一片森林,有兩種操作:詢問兩點之間的第kk小點權和在兩棵樹之間連一條邊。


前置技能:樹上主席樹

做這道題目,我們首先要會樹上主席樹

關於樹上主席樹,這有一道很好的例題:【洛谷2633】Count on a tree(只包含此題的詢問操作)。

LinkLink

【洛谷2633】Count on a tree 的題解 詳見博客 【洛谷2633】Count on a tree(樹上主席樹)

接下來,我們就將詢問操作略過不提了(畢竟在這道題目中已經介紹過了),而主要講講如何連邊。


啓發式合併主席樹

對於連邊操作,我們需要啓發式合併主席樹(當然,也有一些奆佬是用LCTLCT的)。

關於啓發式合併的時間複雜度證明,我寫過一篇很爛的博客,可以去看一下(其實它與按秩合併是同一個東西)。

LinkLink

啓發式合併 詳見博客 啓發式合併

如果要將兩棵主席樹合併,我們一般選擇將深度小的主席樹合併到深度大的主席樹上。

千萬注意,比較的是深度而不是子樹大小!不然會像我一樣TT飛。

至於爲什麼比較深度,在此摘錄hl666hl666神犇的原話如下:

ExcerptExcerpt

影響主席樹時間效率的是深度,而不是子樹大小,子樹大小對時間效率是沒有任何直接影響的。

哦,對了,還沒有講怎麼合併。

其實也非常簡單,直接對深度較小的主席樹重新掃一遍進行更新即可(具體操作見代碼吧)。


代碼

#include<bits/stdc++.h>
#define max(x,y) ((x)>(y)?(x):(y))
#define min(x,y) ((x)<(y)?(x):(y))
#define uint unsigned int
#define LL long long
#define ull unsigned long long
#define swap(x,y) (x^=y,y^=x,x^=y)
#define abs(x) ((x)<0?-(x):(x))
#define INF 1e9
#define Inc(x,y) ((x+=(y))>=MOD&&(x-=MOD))
#define ten(x) (((x)<<3)+((x)<<1))
#define N 80000
#define LogN 20
#define add(x,y) (e[++ee].nxt=lnk[x],e[lnk[x]=ee].to=y)
using namespace std;
int n,m,cnt,ee=0,a[N+5],p[N+5],lnk[N+5],Depth[N+5],fa[N+5][LogN+5];
struct edge
{
    int to,nxt;
}e[(N<<1)+5];
class FIO
{
    private:
        #define Fsize 100000
        #define tc() (FinNow==FinEnd&&(FinEnd=(FinNow=Fin)+fread(Fin,1,Fsize,stdin),FinNow==FinEnd)?EOF:*FinNow++)
        #define pc(ch) (FoutSize<Fsize?Fout[FoutSize++]=ch:(fwrite(Fout,1,FoutSize,stdout),Fout[(FoutSize=0)++]=ch))
        int f,FoutSize,OutputTop;char ch,Fin[Fsize],*FinNow,*FinEnd,Fout[Fsize],OutputStack[Fsize];
    public:
        FIO() {FinNow=FinEnd=Fin;}
        inline void read(int &x) {x=0,f=1;while(!isdigit(ch=tc())) f=ch^'-'?1:-1;while(x=ten(x)+(ch&15),isdigit(ch=tc()));x*=f;}
        inline void read_char(char &x) {while(isspace(x=tc()));}
        inline void read_string(string &x) {x="";while(isspace(ch=tc()));while(x+=ch,!isspace(ch=tc())) if(!~ch) return;}
        inline void write(int x) {if(!x) return (void)pc('0');if(x<0) pc('-'),x=-x;while(x) OutputStack[++OutputTop]=x%10+48,x/=10;while(OutputTop) pc(OutputStack[OutputTop]),--OutputTop;}
        inline void write_char(char x) {pc(x);}
        inline void write_string(string x) {register int i,len=x.length();for(i=0;i<len;++i) pc(x[i]);}
        inline void end() {fwrite(Fout,1,FoutSize,stdout);}
}F;
inline int LCA(int x,int y)//倍增LCA
{
    register int i;
    if(Depth[x]<Depth[y]) swap(x,y);
    for(i=0;Depth[x]^Depth[y];++i) if((Depth[x]^Depth[y])&(1<<i)) x=fa[x][i];
    if(!(x^y)) return x;
    for(i=0;fa[x][i]^fa[y][i];++i);
    for(--i;i>=0;--i) if(fa[x][i]^fa[y][i]) x=fa[x][i],y=fa[y][i];
    return fa[x][0];
}
inline int GetRt(int x)//求出x所在樹的根
{
    register int i;
    for(i=0;Depth[x];++i) if(Depth[x]&(1<<i)) x=fa[x][i];
    return x;
}
class Class_ChairmanTree//主席樹
{
    private:
        int n,tot,Root[N+5];
        struct Tree
        {
            int Val,Size,Level,Son[2];
        }node[N*LogN<<4];
        inline void Build(int l,int r,int &rt)//建樹
        {
            if(!rt&&(rt=++tot),!(l^r)) return;
            register int mid=l+r>>1;
            Build(l,mid,node[rt].Son[0]),Build(mid+1,r,node[rt].Son[1]);
        }
        inline void ins(int l,int r,int &rt,int lst,int val)//插入
        {
            if(node[rt=++tot]=node[lst],++node[rt].Size,!(l^r)) return;
            register int mid=l+r>>1;
            val<=mid?ins(l,mid,node[rt].Son[0],node[lst].Son[0],val):ins(mid+1,r,node[rt].Son[1],node[lst].Son[1],val);
        }
        inline int qry(int l,int r,int rt1,int rt2,int rt3,int rt4,int k)//詢問
        {
            if(!(l^r)) return l;
            register int mid=l+r>>1,t=node[node[rt3].Son[0]].Size+node[node[rt4].Son[0]].Size-node[node[rt1].Son[0]].Size-node[node[rt2].Son[0]].Size;
            if(t>=k) return qry(l,mid,node[rt1].Son[0],node[rt2].Son[0],node[rt3].Son[0],node[rt4].Son[0],k);
            else return qry(mid+1,r,node[rt1].Son[1],node[rt2].Son[1],node[rt3].Son[1],node[rt4].Son[1],k-t);
        }
    public:
        inline void Init(int len) {Build(1,n=len,Root[0]);}//初始化
        inline void Insert(int v,int nv,int val) {ins(1,n,Root[nv],Root[v],val);}
        inline int Query(int v1,int v2,int k) {return qry(1,n,Root[LCA(v1,v2)],Root[fa[LCA(v1,v2)][0]],Root[v1],Root[v2],k);}
        inline bool Smaller(int rt1,int rt2) {return node[rt1].Level<node[rt2].Level;}//判斷rt1所在樹的深度是否小於rt2所在樹的深度
        inline void AddLevel(int rt) {++node[rt].Level;}//將rt所在樹的深度加1
}ChairmanTree;
inline int find(int x)//離散化
{
    register int l=1,r=cnt,mid=l+r>>1;
    for(;l<=r;mid=l+r>>1) p[mid]<x?l=mid+1:r=mid-1;
    return l;
}
inline void Init(int x)//初始化
{
    register int i;
    for(ChairmanTree.Insert(fa[x][0],x,find(a[x])),i=1;i<=LogN;++i) fa[x][i]=fa[fa[x][i-1]][i-1];
    for(i=lnk[x];i;i=e[i].nxt) if(fa[x][0]^e[i].to) Depth[e[i].to]=Depth[x]+1,fa[e[i].to][0]=x,Init(e[i].to);
}
int main()
{
    register int i,x,y,z,fx,fy,Q,ans=0;register char op;
    for(F.read(n),F.read(n),F.read(m),F.read(Q),i=1;i<=n;++i) F.read(a[i]),p[i]=a[i];
    for(i=1;i<=m;++i) F.read(x),F.read(y),add(x,y),add(y,x);
    for(sort(p+1,p+n+1),ChairmanTree.Init(cnt=unique(p+1,p+n+1)-p-1),i=1;i<=n;++i) if(!fa[i][0]) Init(i);
    while(Q--)
    {
    	F.read_char(op),F.read(x),F.read(y);
        if(op^'L') F.read(z),F.write(ans=p[ChairmanTree.Query(x^ans,y^ans,z^ans)]),F.write_char('\n');
        else 
        {
            fx=GetRt(x^=ans),fy=GetRt(y^=ans),add(x,y),add(y,x);//記得連邊
            if(ChairmanTree.Smaller(fx,fy)) swap(fx,fy),swap(x,y);//比較深度,選擇深度較小的樹合併到深度較大的樹上
            ChairmanTree.AddLevel(fx),fa[y][0]=x,Depth[y]=Depth[x]+1,Init(y);//重新建樹
        }
    }
    return F.end(),0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章