题目
题目链接:https://www.luogu.com.cn/problem/P5064
给你一个图,每个点有点权,最开始没有边。
有一些操作:
- 添加一条 (x) 与 (y) 之间的双向边。
- 回到第 (x) 次操作后的状态(注意这里的 (x) 可以是 (0),即回到初始状态)。
- 查询 (x) 所在联通块能到的点中点权第 (y) 小的值,如果不存在,那么输出 (-1)。
(n,mleq 10^5;a_ileq 10^9)。
思路
什么叫做极限卡空间啊(战术后仰
首先这个历史版本很烦,考虑怎么搞掉这个玩意。我们可以建出操作树,那么对于任意一个操作树上的节点 (x),从根节点到 (x) 的所有操作就是它要依次进行的操作。
考虑按照 dfs 序处理询问,那么我们就需要支持加边删边,然后询问连通块 (k) 小值。
加边删边肯定采用按秩合并的并查集,那么询问 (k) 小值一般只有二分和值域分块两种做法,显然前者是不行的。
先把点权离散化,考虑值域分块,其中每一个块的大小为 (T)。对于原图中的任意一个点,我们找到它在并查集中的根节点 (x),记 ( ext{cnt}[x][i]) 表示这个连通块,值域在 ([iT,(i+1)T)) 的点的数量。
操作一就直接把两个连通块按秩合并,操作三的话就先枚举答案在哪一个块,确定块之后再依次枚举块中的所有数字暴力判断。
不算并查集的复杂度的话,那么时间复杂度 (O(mT)),空间复杂度 (O(frac{n^2}{T}))。考虑到空间只有 (20 ext{MB}),所以我们 (T) 必须取大一些。
发现提示中 lxl 的神仙做法复杂度是 (O(frac{nm}{omega})) 的,所以其实 (T) 在 (64) 左右就是可以接受的。所以直接不管时间,卡到 (T=40) 的时候空间就可以接受了。虽然按秩合并并查集理论上是 (O(log n)) 的,但是跑起来还挺快的(
代码
#include <bits/stdc++.h>
using namespace std;
const int N=100010,M=40;
int n,m,T,tot,head[N],ans[N],siz[N],father[N],opt[N],X[N],Y[N];
short cnt[M][N];
struct edge
{
int next,to;
}e[N];
struct node
{
int v,id;
}a[N];
bool cmp(node x,node y)
{
return x.v<y.v;
}
void add(int from,int to)
{
e[++tot]=(edge){head[from],to};
head[from]=tot;
}
inline int read()
{
int d=0; char ch=getchar();
while (!isdigit(ch)) ch=getchar();
while (isdigit(ch)) d=(d<<3)+(d<<1)+ch-48,ch=getchar();
return d;
}
int find(int x)
{
return x==father[x]?x:find(father[x]);
}
inline void merge(int &x,int &y)
{
if (siz[x]<siz[y]) swap(x,y);
father[y]=x; siz[x]+=siz[y];
for (int i=0;i<M;i++) cnt[i][x]+=cnt[i][y];
}
inline void divide(int x,int y)
{
father[y]=y; siz[x]-=siz[y];
for (int i=0;i<M;i++) cnt[i][x]-=cnt[i][y];
}
void dfs(int x)
{
bool flag=0;
if (opt[x]==1)
{
X[x]=find(X[x]),Y[x]=find(Y[x]);
if (X[x]!=Y[x]) merge(X[x],Y[x]),flag=1;
}
if (opt[x]==3)
{
int p=find(X[x]);
if (siz[p]<Y[x]) ans[x]=-1;
else
{
int i=0;
while (Y[x]>cnt[i][p])
Y[x]-=cnt[i][p],i++;
i*=T;
do {
if (find(a[i].id)==p) Y[x]--;
if (!Y[x]) ans[x]=i;
i++;
} while (Y[x]);
}
}
for (int i=head[x];~i;i=e[i].next)
dfs(e[i].to);
if (flag) divide(X[x],Y[x]);
}
int main()
{
memset(head,-1,sizeof(head));
n=read(); m=read(); T=(n-1)/M+1;
for (int i=1;i<=n;i++) a[i]=(node){read(),i};
sort(a+1,a+1+n,cmp);
for (int i=1;i<=n;i++)
father[i]=i,siz[i]=1,cnt[i/T][a[i].id]=1;
tot=0;
for (int i=1;i<=m;i++)
{
opt[i]=read(); X[i]=read();
if (opt[i]==2) add(X[i],i);
if (opt[i]!=2) Y[i]=read(),add(i-1,i);
}
dfs(0);
for (int i=1;i<=m;i++)
if (opt[i]==3)
printf("%d
",ans[i]==-1 ? -1 : a[ans[i]].v);
return 0;
}