zoukankan      html  css  js  c++  java
  • 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
    ",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;
    }
  • 相关阅读:
    最容易被淘汰的八种人
    java基础编程——用两个栈来实现一个队列
    java基础编程——重建二叉树
    java基础——多线程
    java基础编程——链表反转
    java基础——线程池
    java基础——线程
    java基础编程——二维数组中的查找
    网络编程——TCP协议和通信
    网络编程——UDP协议和通信
  • 原文地址:https://www.cnblogs.com/LLTYYC/p/10610584.html
Copyright © 2011-2022 走看看