[SDOI2013]森林(树上主席树)

 ̄綄美尐妖づ 提交于 2020-02-07 10:45:21

[SDOI2013]森林(luogu)

Description

题目描述

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

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

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

为了体现程序的在线性,我们把输入数据进行了加密。设lastans为程序上一次输出的结果,初始的时候lastans为0。

  • 对于一个输入的操作Q x y k,其真实操作为Q x^lastans y^lastans k^lastans
  • 对于一个输入的操作L x y,其真实操作为L x^lastans y^lastans。其中^运算符表示异或,等价于pascal中的xor运算符。

请写一个程序來帮助小Z完成这些操作。

对于所有的数据,n,m,T<=8*10^48104.

输入格式

第一行包含一个正整数testcase,表示当前测试数据的测试点编号。保证1<=testcase<=20。

第二行包含三个整数N,M,T,分别表示节点数、初始边数、操作数。

第三行包含N个非负整数表示 N个节点上的权值。

接下来 M行,每行包含两个整数x和 y,表示初始的时候,点x和点y 之间有一条无向边。

接下来 T行,每行描述一个操作,格式为”Q x y k“或者”L x y “,其含义见题目描述部分。

输出格式

对于每一个第一类操作,输出一个非负整数表示答案。

Solution

对于每个点建立一棵权值线段树,将 它 到 它所在树的根 的路径上每个点的权值放入线段树中

并利用它的父亲的线段树的一部分来优化时间空间

每次连边操作用启发式合并来把复杂度优化到O(logn),修改较小数所有节点的权值线段树,方法同建树时

每次询问操作找lca,再将它们的权值线段树相减,在上面找第k大(注意对lca的处理)

Code

#include <cstdio>
#include <cstdlib>
#include <algorithm>
#include <vector>
using namespace std;
const int N=8e4+10;
struct node
{
    int v,id;
    bool operator <(const node &o)const
    {
        return v<o.v;
    }
}a[N];
int d[N],n,m,T,cnt,re[N],u,v,k,fa[N][17],top[N],si[N],rt[N],tot,dep[N];
vector <int> link[N];
bool flag[N];
char s[5];
struct mode
{
    int lc,rc,sum;
}f[N*400];
void build(int l,int r,int &x,int y,int pos)
{
    x=++tot,f[x]=f[y],f[x].sum++;
    if(l==r) return ;
    int mid=(l+r)>>1;
    if(pos<=mid) build(l,mid,f[x].lc,f[y].lc,pos);
    else build(mid+1,r,f[x].rc,f[y].rc,pos);
}
void dfs(int u,int fx)
{
    flag[u]=true;
    fa[u][0]=fx,si[u]=1,dep[u]=dep[fx]+1;
    for(int i=1;i<17;i++)
        fa[u][i]=fa[fa[u][i-1]][i-1];
    build(1,cnt,rt[u],rt[fx],d[u]);
    int size=link[u].size();
    for(int i=size-1;i>=0;i--)
    {
        int v=link[u][i];
        if(v==fx) continue;
        top[v]=top[u];
        dfs(v,u),si[u]+=si[v];
    }
}
int get_top(int x)
{
    return x==top[x]?x:top[x]=get_top(top[x]);
}
int Lca(int x,int y)
{
    if(dep[x]>dep[y]) swap(x,y);
    for(int i=16;i>=0;i--)
        if(dep[fa[y][i]]>=dep[x]) y=fa[y][i];
    if(y==x) return x;
    for(int i=16;i>=0;i--)
        if(fa[y][i]!=fa[x][i])
            y=fa[y][i],x=fa[x][i];
    return fa[x][0];
}
int get(int lca1,int lca2,int x,int y,int l,int r,int k)
{
    if(l==r) return l;
    int mid=(l+r)>>1;
    int cn=f[f[x].lc].sum+f[f[y].lc].sum-f[f[lca1].lc].sum-f[f[lca2].lc].sum;
    if(k<=cn) return get(f[lca1].lc,f[lca2].lc,f[x].lc,f[y].lc,l,mid,k);
    else return get(f[lca1].rc,f[lca2].rc,f[x].rc,f[y].rc,mid+1,r,k-cn);
}
int main()
{
    scanf("%d",&T);
    scanf("%d%d%d",&n,&m,&T);
    for(int i=1;i<=n;i++)
        scanf("%d",&a[i].v),a[i].id=i;
    sort(a+1,a+1+n);
    for(int i=1;i<=n;i++)
    {
        if(i==1 || a[i].v!=a[i-1].v) re[++cnt]=a[i].v;
        d[a[i].id]=cnt;
    }
    while(m--)
    {
        scanf("%d%d",&u,&v);
        link[u].push_back(v);
        link[v].push_back(u);
    }
    for(int i=1;i<=n;i++)
        if(!flag[i]) top[i]=i,dfs(i,0);
    int last=0;
    while(T--)
    {
        scanf("%s%d%d",s,&u,&v);
        u^=last,v^=last;
        if(s[0]=='L')
        {
            link[u].push_back(v);
            link[v].push_back(u);
            int fu=get_top(u),fv=get_top(v);
            if(si[fu]<si[fv]) swap(fu,fv),swap(u,v);
            top[v]=fu,si[fu]+=si[fv];
            dfs(v,u);
        }
        else
        {
            scanf("%d",&k);
            k^=last;
            int lca=Lca(u,v);
            last=re[get(rt[fa[lca][0]],rt[lca],rt[u],rt[v],1,cnt,k)];
            printf("%d\n",last);
        }
    }
    return 0;
}

 

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!