• [BZOJ 3123] 森林


    Link:

    BZOJ 3123 传送门

    Solution:

    主席树+启发式合并

    以前好像做的主席树都是在序列上的……在树上的主席树这样处理:

    每个节点的主席树维护其到根节点的路径上的值,以其父节点为模板构造

    那么为了取出$(u,v)$路径上的值用$seg[x]+seg[y]-seg[lca]-seg[f[lca]]$就行了

    这其实可以说是树上差分的基本套路,而主席树不就是利用了差分性质嘛

    接下来就要处理合并了,有一种感性方式就是将小的连通块并入大的中

    这样的方式就叫做启发式合并了($DSU on Tree$),可以证明其总复杂度为$O(n*log(n))$

    证明:

    对于每棵以$v$为根的子树而言,仅当$edge(v,f[v])$为轻边时才会将所有子树内节点更新

    如果按每个点计算贡献的方式,根据树链剖分中的性质:一个点到根的路径上轻边/重链个数不超过$log(n)$

    可以发现每个节点最多更新$log(n)$次,因此复杂度上界就是$n*log(n)$

    实现时用并查集维护连通性,每次找到$x,y$的代表节点$f[x],f[y]$,假设$sz[x]<sz[y]$

    合并时图中将$x,y$连边,并查集中将$f[x],f[y]$连边

    并将以$x$为根的子树中的点以$y$节点为初始点进行暴力更新

    根据上述证明每个点保证最多更新$log(n)$次,但更新主席树也要$log(n)$,因此总复杂度为$O(n*log(n)^2)$

    Tip:这次空间又开小了……难道不是$O(n*log(n))$?以后是真要学学怎么算空间复杂度了

    Code:

    #include <bits/stdc++.h>
    
    using namespace std;
    const int MAXN=8e4+10,MAXM=20000005;
    struct PrTree{int ls,rs,cnt;}seg[MAXM];//一开始空间又开小了…… 
    struct edge{int nxt,to;}e[MAXN<<2];
    int f[MAXN][25],dep[MAXN],sz[MAXN],fa[MAXN];
    int T,n,m,t,x,y,k,rt[MAXN],head[MAXN],dat[MAXN],dsp[MAXN],etot,tot,cnt,res;
    
    void add_edge(int from,int to)
    {e[++etot].nxt=head[from];e[etot].to=to;head[from]=etot;}
    
    int LCA(int x,int y)
    {
        if(dep[x]<dep[y]) swap(x,y);
        int t=dep[x]-dep[y];
        for(int i=0;i<=20;i++)
            if(t&(1<<i)) x=f[x][i];
        for(int i=20;~i;i--)
            if(f[x][i]!=f[y][i])
                x=f[x][i],y=f[y][i];
        return (x==y)?x:f[x][0];
    }
    
    void Insert(int pre,int &cur,int pos,int l,int r)
    {
        cur=++cnt;seg[cur]=seg[pre];seg[cur].cnt++;
        if(l==r) return;int mid=(l+r)>>1;
        if(pos<=mid) Insert(seg[pre].ls,seg[cur].ls,pos,l,mid);
        else Insert(seg[pre].rs,seg[cur].rs,pos,mid+1,r);
    }
    
    int Query(int a,int b,int c,int d,int k,int l,int r)
    {
        if(l==r) return dsp[l];
        int mid=(l+r)>>1,sum=seg[seg[a].ls].cnt+seg[seg[b].ls].cnt-seg[seg[c].ls].cnt-seg[seg[d].ls].cnt;
        if(k<=sum) return Query(seg[a].ls,seg[b].ls,seg[c].ls,seg[d].ls,k,l,mid);
        else return Query(seg[a].rs,seg[b].rs,seg[c].rs,seg[d].rs,k-sum,mid+1,r);
    }
    
    int Find(int x){return (fa[x]==x)?x:fa[x]=Find(fa[x]);}
    void dfs(int x,int anc)
    {
        dep[x]=dep[anc]+1;f[x][0]=anc;
        for(int i=1;i<=20;i++) f[x][i]=f[f[x][i-1]][i-1];
        Insert(rt[anc],rt[x],dat[x],1,tot);
        for(int i=head[x];i;i=e[i].nxt)
            if(e[i].to!=anc) dfs(e[i].to,x);
    }
    void Link(int x,int y)
    {
        int posx=Find(x),posy=Find(y);
        if(sz[posx]>sz[posy]) swap(x,y),swap(posx,posy);
        sz[posy]+=sz[posx];fa[posx]=posy;
        add_edge(x,y);add_edge(y,x);dfs(x,y);
    }
    
    int main()
    {
        scanf("%d%d%d%d",&T,&n,&m,&t);
        for(int i=1;i<=n;i++) scanf("%d",&dat[i]),dsp[i]=dat[i];
        sort(dsp+1,dsp+n+1);tot=unique(dsp+1,dsp+n+1)-dsp-1;
        for(int i=1;i<=n;i++) dat[i]=lower_bound(dsp+1,dsp+n+1,dat[i])-dsp;
        
        for(int i=1;i<=n;i++) sz[i]=1,fa[i]=i;
        for(int i=1;i<=m;i++)
            scanf("%d%d",&x,&y),Link(x,y);
        for(int i=1;i<=n;i++)//一定要对其它孤立点初始化 
            if(!dep[i]) dfs(i,0);
        
        for(int i=1;i<=t;i++)
        {
            char s[20];scanf("%s%d%d",s,&x,&y);
            x^=res;y^=res;
            if(s[0]=='Q')
            {
                scanf("%d",&k);k^=res;
                int lca=LCA(x,y),flca=f[lca][0];
                printf("%d
    ",res=Query(rt[x],rt[y],rt[lca],rt[flca],k,1,tot));
            }
            else Link(x,y);
        }
        return 0;
    }
  • 相关阅读:
    JavaScriptFunction对象(函数)的声明和作用域
    JavaScript常用对象的属性及方法(2)
    html+css>backgroundimg(背景图的设置)
    JavaScript知识点>运算规则与运算(逻辑、位)
    JavaScript知识点:分支结构(if、switch)+算法例题
    JavaScript常用对象的属性及方法(1)
    一些学习js的算法题目
    隐马尔科夫模型
    Eclipse插件开发小结
    二叉树排序和堆排序
  • 原文地址:https://www.cnblogs.com/newera/p/9357355.html
Copyright © 2020-2023  润新知