【洛谷5064】[Ynoi2014] 等这场战争结束之后(操作树+值域分块)

点此看题面

  • 给定一张(n)个点的图,初始没有边。
  • 要求支持三种操作:加入一条无向边;回到之前某次操作后的状态;询问一个连通块的第(k)小值。
  • (n,qle10^5)

操作树上(dfs)

这类问题的经典操作。

只要建出操作树,就只需要考虑一次操作的加入与撤销即可。

值域分块+并查集

首先这道题必须要离散化,但离散化时我们不应去重,这样就可以保证离散化后的值与原数一一对应(然而好像数据里面并没有重复的数)。

然后把值域分块,对于每个连通块维护每个块的数的个数。

询问答案时先找到答案在哪个块,再枚举这个块中的每个值,找到对应点判断是否与询问点连通(可以使用并查集维护)。

注意这道题卡内存,由于一个值域块内的数不超过(S)个可以开成(short),那么块长(S)可以取到(2500),块个数为(40)

代码:(O(nsqrt{nlogn}))

#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 100000
#define BS 2500
#define BT 40
#define add(x,y) (e[++ee].nxt=lnk[x],e[lnk[x]=ee].to=y)
using namespace std;
int n,Qt,a[N+5],id[N+5];struct Q {int op,x,y;}q[N+5];
int ee,lnk[N+5];struct edge {int to,nxt;}e[N+5];
namespace FastIO
{
	#define FS 100000
	#define tc() (FA==FB&&(FB=(FA=FI)+fread(FI,1,FS,stdin),FA==FB)?EOF:*FA++)
	#define pc(c) (FC==FE&&(clear(),0),*FC++=c)
	int OT;char oc,FI[FS],FO[FS],OS[FS],*FA=FI,*FB=FI,*FC=FO,*FE=FO+FS;
	I void clear() {fwrite(FO,1,FC-FO,stdout),FC=FO;}
	Tp I void read(Ty& x) {x=0;W(!isdigit(oc=tc()));W(x=(x<<3)+(x<<1)+(oc&15),isdigit(oc=tc()));}
	Ts I void read(Ty& x,Ar&... y) {read(x),read(y...);}
	Tp I void writeln(Ty x) {W(OS[++OT]=x%10+48,x/=10);W(OT) pc(OS[OT--]);pc('
');}
	I void NA() {pc('-'),pc('1'),pc('
');}
}using namespace FastIO;
namespace V//值域分块
{
	int sz,bl[N+5],p[N+5];short c[N+1][BT+1];
	I void U(CI x,CI y,CI op) {for(RI i=1;i<=bl[n];++i) c[x][i]+=op*c[y][i];}//给x的数组加上/减去y的数组
	int dv[N+5],nw[N+5];I void Build()//预处理
	{
		RI i,x;for(sz=min(BS,n),i=1;i<=n;++i) bl[i]=(i-1)/sz+1;for(i=1;i<=n;++i) dv[i]=a[i];sort(dv+1,dv+n+1);
		for(i=1;i<=n;++i) c[i][bl[a[i]=nw[x=lower_bound(dv+1,dv+n+1,a[i])-dv]?++nw[x]:(nw[x]=x)]]=1,p[a[i]]=i;//不去重离散化,并对应到原数
	}
}
namespace U//按秩合并可撤销并查集
{
	int f[N+5],g[N+5],T,Sx[N+5],Sy[N+5];I int fa(CI x) {return f[x]?fa(f[x]):x;}
	I void Init() {for(RI i=1;i<=n;++i) g[i]=1;}//初始赋值
	I void Union(RI x,RI y)//合并
	{
		++T;if((x=fa(x))==(y=fa(y))) return (void)(Sx[T]=-1);//已连通则合并无效
		g[x]<g[y]&&(swap(x,y),0),g[Sx[T]=f[y]=x]+=g[Sy[T]=y],V::U(x,y,1);
	}
	I void Back() {~Sx[T]&&(g[Sx[T]]-=g[Sy[T]],V::U(Sx[T],Sy[T],-1),f[Sy[T]]=0),--T;}//撤销
}
I int Q(RI x,RI y)//询问x所在连通块第y大
{
	if(U::g[x=U::fa(x)]<y) return -1;RI i;for(i=1;V::c[x][i]<y;++i) y-=V::c[x][i];//找到在哪个块
	for(i=(i-1)*V::sz+1;;++i) if(U::fa(V::p[i])==x&&!--y) return i;//枚举块中值找到对应点判连通
}
int ans[N+5];I void dfs(CI x)//操作树上dfs
{
	q[x].op==1&&(U::Union(q[x].x,q[x].y),0),q[x].op==3&&(ans[x]=Q(q[x].x,q[x].y));//处理当前点操作
	for(RI i=lnk[x];i;i=e[i].nxt) dfs(e[i].to);q[x].op==1&&(U::Back(),0);//遍历子树,撤销操作
}
int main()
{
	RI i,op,x;for(read(n,Qt),i=1;i<=n;++i) read(a[i]);V::Build();
	for(i=1;i<=Qt;++i) read(op),op^2?(q[i].op=op,read(q[i].x,q[i].y),add(id[i-1],i),id[i]=i):(read(x),id[i]=id[x]);//id[i]记录第i个操作后对应节点
	for(U::Init(),dfs(0),i=1;i<=Qt;++i) q[i].op==3&&(~ans[i]?writeln(V::dv[ans[i]]):NA(),0);return clear(),0;
}
败得义无反顾,弱得一无是处
原文地址:https://www.cnblogs.com/chenxiaoran666/p/Luogu5064.html