P3302 [SDOI2013] 森林
P3302 [SDOI2013] 森林
题目描述
小 Z 有一片森林,含有 \(N\) 个节点,每个节点上都有一个非负整数作为权值。初始的时候,森林中有 \(M\) 条边。
小Z希望执行 \(T\) 个操作,操作有两类:
Q x y k查询点 \(x\) 到点 \(y\) 路径上所有的权值中,第 \(k\) 小的权值是多少。此操作保证点 \(x\) 和点 \(y\) 连通,同时这两个节点的路径上至少有 \(k\) 个点。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 完成这些操作。
输入格式
第一行包含一个正整数 \(\mathrm{testcase}\),表示当前测试数据的测试点编号。
第二行包含三个整数 \(N,M,T\),分别表示节点数、初始边数、操作数。
第三行包含 \(N\) 个非负整数表示 \(N\) 个节点上的权值。
接下来 \(M\) 行,每行包含两个整数 \(x\) 和 \(y\),表示初始的时候,点 \(x\) 和点 \(y\) 之间有一条无向边。
接下来 \(T\) 行,每行描述一个操作,格式为 Q x y k 或者 L x y,其含义见题目描述部分。
输出格式
对于每一个第一类操作,输出一个非负整数表示答案。
数据范围
对于 \(100\%\) 的测试数据,所有节点的编号在 \(1\sim N\) 的范围内。节点上的权值 \(\le 10^9\)。\(M<N\)。\(N\le 8\times 10^4\)。
Solution:
看到题目,花费 0.1 个思考想到了 LCT 然后发现“路径上权值第 k 大”。是 LCT 无法战胜的东西了。
然后又花费 0.5 个思考,发现 “路径上权值第 k 大” 显然是主席树的活啊。然后我们发现他只 link ,不 cut 的啊!!! 并且我们还发现他还保证了连完边之后是一个森林。这就完美符合了我们使用线段树的设想。
首先,用主席树维护路径上权值第 k 大的做法很模板,就是每次二分一个数 \(val\) ,然后查询一下路径上满足 \(i<val\) 的 \(i\) 的个数 \(cnt\)。 然后我们取出路径上的点的方法也很简单,就是线段树上的每个节点对应原树的各个节点,线段树节点的区间下存有其子树内所有节点的权值。取出一条路径时,由于我们并不关心满足条件的 \(i\) 的具体权值,我们只需要将 \(x,y,lca,lca'father\) 这四个点的cnt 按照 \(cnt_x+cnt_y-cnt_lca-cnt_{lca'sfather}\) 来做一个差分就好了。
接下来是连边操作:我们如果每次连边都直接将两个联通块暴力合并然后扫描一遍,那么时间复杂度是很容易爆炸的。但是我们发现,在无向图中添加一条边来合并两个联通块时,这条在森林上对应的父子关系其实是不重要的,所以我们每次合并时只需要将 \(size\) 小的联通块重新扫描一遍,暴力合并到大的联通块上就好了,这样做的时间复杂度是 \(O(nlogn)\) 的(其实就是 dsu on tree)
Code:
#include<bits/stdc++.h>
const int N=8e4+5;
const int lg=20;
using namespace std;
struct Edge{
int to,nxt;
}e[N<<2];
int n,m,T,t_cnt,e_cnt,tot,ans;
int head[N],a[N],b[N],dep[N],siz[N],root[N],fa[N];
int f[N][lg+5];
char c[999];
int id(int x)
{
return lower_bound(b+1,b+1+n,x)-b;
}
int find(int x)
{
fa[x] = fa[x]==x ? x :find(fa[x]);
return fa[x];
}
void add(int x,int y)
{
e[++e_cnt]=(Edge){y,head[x]};
head[x]=e_cnt;
}
struct Tree{
int ls,rs,cnt;
}t[N*170];
void build(int &now,int l,int r)
{
now=++t_cnt;
if(l==r)return;
int mid=l+r>>1;
build(t[now].ls,l,mid);
build(t[now].rs,mid+1,r);
return ;
}
void insert(int &now,int pre,int l,int r,int k)
{
t[now=++t_cnt]=t[pre];
t[now].cnt++;
if(l==r)return ;
int mid=l+r>>1;
if(k<=mid)insert(t[now].ls,t[pre].ls,l,mid,k);
else insert(t[now].rs,t[pre].rs,mid+1,r,k);
}
int query(int x,int y,int lca,int f_lca,int l,int r,int k)
{
if(l==r)return b[l];
int val=t[t[x].ls].cnt+t[t[y].ls].cnt-t[t[lca].ls].cnt-t[t[f_lca].ls].cnt;
int mid=l+r>>1;
if(k<=val)return query(t[x].ls,t[y].ls,t[lca].ls,t[f_lca].ls,l,mid,k);
else return query(t[x].rs,t[y].rs,t[lca].rs,t[f_lca].rs,mid+1,r,k-val);
}
void dfs(int x,int y,int rt)
{
f[x][0]=y;
for(int i=1;i<=lg;i++)
{
f[x][i]=f[f[x][i-1]][i-1];
}
siz[rt]++;
fa[x]=y;
dep[x]=dep[y]+1;
insert(root[x],root[y],1,tot,id(a[x]));
for(int i=head[x];i;i=e[i].nxt)
{
int to=e[i].to;
if(to==y)continue;
dfs(to,x,rt);
}
}
int LCA(int x,int y)
{
if(x==y)return x;
if(dep[x]<dep[y])swap(x,y);
for(int i=lg;~i;i--)
{
if(dep[f[x][i]]>=dep[y])x=f[x][i];
}
if(x==y)return x;
for(int i=lg;~i;i--)
{
if(f[x][i]!=f[y][i])x=f[x][i],y=f[y][i];
}
return f[x][0];
}
void work()
{
cin>>n;
cin>>n>>m>>T;
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
b[i]=a[i];
fa[i]=i;
}
sort(b+1,b+1+n);
tot=unique(b+1,b+1+n)-(b+1);
build(root[0],1,tot);
for(int i=1,x,y;i<=m;i++)
{
scanf("%d%d",&x,&y);
add(x,y);
add(y,x);
}
//cout<<111;
for(int i=1;i<=n;i++)
{
if(!dep[i])
{
dfs(i,0,i);
fa[i]=i;
}
}
for(int i=1,x,y,k;i<=T;i++)
{
scanf("%s",c);
if(c[0]=='Q')
{
scanf("%d%d%d",&x,&y,&k);
x^=ans,y^=ans,k^=ans;
int lca=LCA(x,y);
ans=query(root[x],root[y],root[lca],root[f[lca][0]],1,tot,k);
printf("%d\n",ans);
}
else
{
scanf("%d%d",&x,&y);
x^=ans,y^=ans;
int xx=find(x),yy=find(y);
if(siz[xx]<siz[yy]){swap(x,y),swap(xx,yy);}
add(x,y),add(y,x);
dfs(y,x,xx);
}
}
}
int main()
{
//freopen("forest.in","r",stdin);freopen("forest.out","w",stdout);
work();
return 0;
}

浙公网安备 33010602011771号