zoukankan      html  css  js  c++  java
  • [SDOI2013]森林

    Description:

    小Z有一片森林,含有N个节点,每个节点上都有一个非负整数作为权值。初始的时候,森林中有M条边。

    小Z希望执行T个操作,操作有两类:

    (1.Q x y k)查询点(x)到点(y)路径上所有的权值中,第k小的权值是多少。此操作保证点x和点y连通,同时这两个节点的路径上至少有k个点。
    (2.L x y)在点(x)和点(y)之间连接一条边。保证完成此操作后,仍然是一片森林

    Hint:

    $n,m,T<=8*10^{4} $

    Solution:

    可以发现询问就是(Count On A Tree)的询问,现在问题是如何动态维护森林
    联想到启发式合并,每次暴力在较小的树上重建主席树和倍增数组,一个(log)还是可以承受的

    #include<bits/stdc++.h>
    using namespace std;
    const int mxn=8e4+5,mxm=2e7+5;
    struct ed {
        int to,nxt;
    }t[mxn<<1];
    int n,m,q,s,ans,tot,cnt;
    int a[mxn],b[mxn],dep[mxn],anc[mxn],sum[mxm],vis[mxn],hd[mxn],sz[mxn],ls[mxm],rs[mxm],rt[mxm],f[mxn][19];
    
    inline void add(int u,int v) {
        t[++cnt]=(ed){v,hd[u]},hd[u]=cnt;
    }
    
    int find(int x) {
        return anc[x]==x?x:anc[x]=find(anc[x]);
    }
    
    void update(int,int&,int,int,int);
    
    void dfs(int u,int fa)
    {
        f[u][0]=fa; dep[u]=dep[fa]+1; vis[u]=1;
        update(rt[fa],rt[u],1,s,b[u]);
        for(int i=1;i<=18;++i) f[u][i]=f[f[u][i-1]][i-1];
        for(int i=hd[u];i;i=t[i].nxt) {
            int v=t[i].to;
            if(v==fa) continue ;
            dfs(v,u);
        }
    }
    
    void update(int las,int& p,int l,int r,int val)
    {
        p=++tot; sum[p]=sum[las]+1;
        if(l==r) return ; int mid=(l+r)>>1;
        if(val<=mid) update(ls[las],ls[p],l,mid,val),rs[p]=rs[las];
        else update(rs[las],rs[p],mid+1,r,val),ls[p]=ls[las];
    }
    
    int query(int las1,int las2,int p1,int p2,int l,int r,int k)
    {
        if(l==r) return a[l]; int mid=(l+r)>>1;
        int tp=sum[ls[p1]]+sum[ls[p2]]-sum[ls[las1]]-sum[ls[las2]];
        if(k<=tp) return query(ls[las1],ls[las2],ls[p1],ls[p2],l,mid,k);
        else return query(rs[las1],rs[las2],rs[p1],rs[p2],mid+1,r,k-tp);
    }
    
    void build(int &p,int l,int r,int val)
    {
        p=++tot; sum[p]=1;
        if(l==r) return ; int mid=(l+r)>>1;
        if(val<=mid) build(ls[p],l,mid,val);
        else build(rs[p],mid+1,r,val);
    }
    
    int LCA(int x,int y)
    {
        if(dep[x]<dep[y]) swap(x,y);
        for(int i=18;i>=0;--i) 
            if(dep[f[x][i]]>=dep[y])
                x=f[x][i];
        if(x==y) return x;
        for(int i=18;i>=0;--i)
            if(f[x][i]!=f[y][i])
                x=f[x][i],y=f[y][i];
        return f[x][0];
    }
    
    void link(int& u,int& v)
    {
        u^=ans,v^=ans;
        add(u,v),add(v,u);
        int x=find(u),y=find(v);
        if(sz[x]>sz[y]) swap(x,y),swap(u,v);
        anc[x]=y; sz[y]+=sz[x]; 
    }
    
    void solve(int u,int v,int k)
    {
        u^=ans,v^=ans,k^=ans;
        int lca=LCA(u,v);
        ans=query(rt[lca],rt[f[lca][0]],rt[u],rt[v],1,s,k);
        printf("%d
    ",ans);
    }
    
    int main()
    {
        int u,v,w; char opt[4];
        scanf("%d%d%d%d",&u,&n,&m,&q);
        for(int i=1;i<=n;++i) sz[i]=1,anc[i]=i;
        for(int i=1;i<=n;++i) scanf("%d",&a[i]),b[i]=a[i];
        sort(a+1,a+n+1); s=unique(a+1,a+n+1)-a-1;
        for(int i=1;i<=n;++i) 
            b[i]=lower_bound(a+1,a+n+1,b[i])-a,build(rt[i],1,s,b[i]);
        for(int i=1;i<=m;++i) {
            scanf("%d%d",&u,&v);
            link(u,v); //这里有个细节,如果一开始就
        }
        for(int i=1;i<=n;++i) if(!vis[find(i)]) dfs(find(i),0);
        
        for(int i=1;i<=q;++i) {
            scanf("%s",opt);
            if(opt[0]=='Q') 
                scanf("%d%d%d",&u,&v,&w),solve(u,v,w);
            else 
                scanf("%d%d",&u,&v),link(u,v),dfs(u,v);
        }
        
        return 0;
    }
    
    
  • 相关阅读:
    开放就像死亡访问之后就能回头——Leo鉴书84
    将博客搬至CSDN
    将博客搬至CSDN
    滚动条
    Perl Pack写的一个数据报表程序
    利用hash 数组打印标题
    Linux显示只显示目录文件
    Linux显示按文件名降序文件
    Linux显示以时间生升序显示文件
    Linux显示按文件大小降序排列
  • 原文地址:https://www.cnblogs.com/list1/p/10374805.html
Copyright © 2011-2022 走看看