【洛谷P5064】等这场战争结束之后

题目

题目链接:https://www.luogu.com.cn/problem/P5064
给你一个图,每个点有点权,最开始没有边。
有一些操作:

  1. 添加一条 \(x\)\(y\) 之间的双向边。
  2. 回到第 \(x\) 次操作后的状态(注意这里的 \(x\) 可以是 \(0\),即回到初始状态)。
  3. 查询 \(x\) 所在联通块能到的点中点权第 \(y\) 小的值,如果不存在,那么输出 \(-1\)

\(n,m\leq 10^5;a_i\leq 10^9\)

思路

什么叫做极限卡空间啊(战术后仰
首先这个历史版本很烦,考虑怎么搞掉这个玩意。我们可以建出操作树,那么对于任意一个操作树上的节点 \(x\),从根节点到 \(x\) 的所有操作就是它要依次进行的操作。
考虑按照 dfs 序处理询问,那么我们就需要支持加边删边,然后询问连通块 \(k\) 小值。
加边删边肯定采用按秩合并的并查集,那么询问 \(k\) 小值一般只有二分和值域分块两种做法,显然前者是不行的。
先把点权离散化,考虑值域分块,其中每一个块的大小为 \(T\)。对于原图中的任意一个点,我们找到它在并查集中的根节点 \(x\),记 \(\text{cnt}[x][i]\) 表示这个连通块,值域在 \([iT,(i+1)T)\) 的点的数量。
操作一就直接把两个连通块按秩合并,操作三的话就先枚举答案在哪一个块,确定块之后再依次枚举块中的所有数字暴力判断。
不算并查集的复杂度的话,那么时间复杂度 \(O(mT)\),空间复杂度 \(O(\frac{n^2}{T})\)。考虑到空间只有 \(20\text{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\n",ans[i]==-1 ? -1 : a[ans[i]].v);
	return 0;
}
posted @ 2021-05-16 15:39  stoorz  阅读(53)  评论(0编辑  收藏  举报