P3302 [SDOI2013]森林

传送门

看到森林有合并首先会想到 $LCT$ ,然后发现链上第 $K$ 小不可维护

感觉 $LCT$ 只维护合并也有点大材小用了,考虑合并时直接启发式合并就可以不用 $LCT$

然后求第 $K$ 小显然考虑主席树

对每个节点维护一个主席树,维护它到树根这的一段区间,那么当前节点的线段树可以直接借用父节点的线段树

并且因为主席树是可加减的,设节点 $x$ 的线段树为 $T[x]$,那么询问是就是在 $T[x]+T[y]-T[lca(x,y)]-T[fa[lca(x,y)]]$ 上面跑

每次启发式合并时把小的子树重构一波

因为值的范围很大所以要离散一下

更新一个节点的复杂度为 $O(log_n)$ ,启发式合并总复杂度为 $O(nlog_n)$

那么总复杂度 $O(nlog^2_n)$

 

最后注意一个可能只有我才要注意的细节:

如果预处理倍增数组是这样:

f[x][0]=fa[x]; for(int i=1;(1<<i-1)<=dep[x];i++) f[x][i]=f[f[x][i-1]][i-1];

并且查询时是这样:

for(int i=20;i>=0;i--) if(dep[f[x][i]]>=dep[y]) x=f[x][i];
if(x==y) return x;
for(int i=20;i>=0;i--)
    if(f[x][i]!=f[y][i]) x=f[x][i],y=f[y][i];

那么就会 $GG$

本来这个板子在静态的树上是没问题的

但是我们合并子树时可能一个节点的 $dep$ 会变小,那么就无法完全覆盖上一次的数据

而查询时又是从最后面开始查,然后就会访问到以前的数据,直接导致 $GG$

查了将近两个小时......

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<cstring>
#include<queue>
using namespace std;
typedef long long ll;
inline int read()
{
    int x=0,f=1; char ch=getchar();
    while(ch<'0'||ch>'9') { if(ch=='-') f=-1; ch=getchar(); }
    while(ch>='0'&&ch<='9') { x=(x<<1)+(x<<3)+(ch^48); ch=getchar(); }
    return x*f;
}
const int N=4e6+7,M=5e7+7;

int fir[N],from[N<<1],to[N<<1],cntt;//存森林
inline void add(int a,int b)
{
    from[++cntt]=fir[a]; fir[a]=cntt; to[cntt]=b;
    from[++cntt]=fir[b]; fir[b]=cntt; to[cntt]=a;
}

int n,m,Q,tot;
int a[N],b[N];//a是原来的,b是离散化后的
inline int find(int x) { return lower_bound(b+1,b+tot+1,x)-b; }//在离散化后的数据中找到相应的位置
//以下主席树
int rt[N],L[M<<1],R[M<<1],T[M<<1],cnt;
int pos,K,res;
void insert(int &o,int l,int r,int pre)
{
    if(!o) o=++cnt; T[o]=T[pre]+1;
    if(l==r) return;
    int mid=l+r>>1;
    if(pos<=mid) insert(L[o],l,mid,L[pre]),R[o]=R[pre];//另一边直接覆盖
    else insert(R[o],mid+1,r,R[pre]),L[o]=L[pre];
}
void query(int x,int y,int lca,int flca,int l,int r)
{
    if(l==r) { res=b[l]; return; }
    int mid=l+r>>1,t=T[L[x]]+T[L[y]]-T[L[lca]]-T[L[flca]];//
    if(t<K) { K-=t; query(R[x],R[y],R[lca],R[flca],mid+1,r); }
    else query(L[x],L[y],L[lca],L[flca],l,mid);
}
//以下维护森林中的数据
int RT[N],RTsz[N],fa[N],dep[N],f[N][27];
//RT维护每个节点的根,RTsz维护一个根节点的子树大小,fa为父节点,dep为深度,f是倍增数组
bool vis[N];//vis用来判断一个点是否访问过,只为第一次预处理
void dfs(int x,int rrt)//dfs更新主席树和倍增数组
{
    pos=find(a[x]); insert(rt[x],1,tot,rt[fa[x]]);//直接修改
    vis[x]=1; RT[x]=rrt; RTsz[rrt]++;
    dep[x]=dep[fa[x]]+1; f[x][0]=fa[x];
    for(int i=1;i<=20;i++) f[x][i]=f[f[x][i-1]][i-1];//注意要全部覆盖!
    for(int i=fir[x];i;i=from[i])
    {
        int &v=to[i]; if(v==fa[x]) continue;
        fa[v]=x; dfs(v,rrt);
    }
}
inline int LCA(int x,int y)//求LCA
{
    if(dep[x]<dep[y]) swap(x,y);
    for(int i=20;i>=0;i--) if(dep[f[x][i]]>=dep[y]) x=f[x][i];
    if(x==y) return x;
    for(int i=20;i>=0;i--)
        if(f[x][i]!=f[y][i]) x=f[x][i],y=f[y][i];
    return f[x][0];
}

int tmp[N];
int main()
{
    int x=read(),y,z;
    n=read(),m=read(),Q=read();
    for(int i=1;i<=n;i++) tmp[i]=a[i]=read();
    sort(tmp+1,tmp+n+1);
    for(int i=1;i<=n;i++) if(i==1||tmp[i]!=tmp[i-1]) b[++tot]=tmp[i];//离散化
    for(int i=1;i<=m;i++) x=read(),y=read(),add(x,y);//连边
    for(int i=1;i<=n;i++) if(!vis[i]) dfs(i,i);//预处理
    int pre=0; char s[7];
    while(Q--)
    {
        scanf("%s",s); x=read()^pre,y=read()^pre;
        if(s[0]=='Q')
        {
            z=read()^pre; int lca=LCA(x,y);
            res=0; K=z; query(rt[x],rt[y],rt[lca],rt[fa[lca]],1,tot);
            printf("%d\n",res); pre=res;
            continue;
        }
        if(RTsz[RT[x]]<RTsz[RT[y]]) swap(x,y);//启发式合并
        add(x,y); fa[y]=x; dfs(y,RT[x]);
    }
    return 0;
}

 

posted @ 2019-03-27 20:44  LLTYYC  阅读(194)  评论(0编辑  收藏  举报