[Ynoi2014] 等这场战争结束之后
非常暴力的做法:每个点维护一颗平衡树,然后启发式合并。
但是这样的暴力做法会被回溯操作卡飞天。
先建出一棵操作树,将回溯操作简化为回退一次操作。
思考平衡树的劣势在哪里,合并和询问的复杂度极其不平衡。
想到平衡复杂度,那就只好分块了。
每个点维护一个值域分块,记录 \(cnt_{i, j}\) 为 \(i\) 点为根的子树中第 \(j\) 个值域块有多少个数。
连边和退边操作直接用可撤销并查集维护,合并/分裂时将两个根之间的贡献相加/减去。
询问时直接用根的 \(cnt\) 找到在哪个值域块,然后暴力遍历在找到值域块的所有数(包括不在当前树内的),注意离散化的时候不能去重,否则复杂度不正确。
那么你大概得到了一个时间 \(O(nB\log n)\),空间 \(O(\frac{n^2}{B})\) 的东西。
发现这题卡空间,把 \(B\) 调大一点即可。还是过不去的话,注意 \(\frac{n}{B}\) 不是很大,把 \(cnt\) 开 short 立省一半空间。
Code:
#include <stdio.h>
#include <algorithm>
#include <string.h>
#include <cctype>
#include <vector>
#include <queue>
#include <bitset>
#define vi vector<int>
#define pb push_back
#define mp make_pair
#define st first
#define nd second
using namespace std;
typedef long long ll;
typedef pair <int, int> Pii;
const int INF=0x3f3f3f3f;
const int cp=998244353;
inline int mod(int x){if(x>=cp) x-=cp;if(x<0) x+=cp;return x;}
inline void plust(int &x, int y){x=mod(x+y);return ;}
inline void minut(int &x, int y){x=mod(x-y);return ;}
inline int read(){
char ch=getchar();int x=0, f=1;
while(!isdigit(ch)){if(ch=='-') f=-1; ch=getchar();}
while(isdigit(ch)){x=(x<<3)+(x<<1)+ch-'0';ch=getchar();}
return x*f;
}
inline void write(int x){
if(x<0) putchar('-'), x=-x;
if(x>9) write(x/10);
putchar(x%10+'0');
}
inline int ksm(int a, int b=cp-2){
int ret=1;
for(; b; b>>=1, a=1ll*a*a%cp)
if(b&1) ret=1ll*ret*a%cp;
return ret;
}
const int N=1e5+5;
const int M=4000;
int n, m, X[N], Y[N], bid[N], L[25], R[25], a[N], id[N], ans[N];
int siz[N], f[N], p[N], num[N], vis[N];
short cnt[N][25];
int find(int x){return (x^f[x])?find(f[x]):f[x];}
vi G[N], rub;
void calc(int x, int y, int op){for(int i=bid[1]; i<=bid[n]; ++i) cnt[x][i]+=op*cnt[y][i];}
bool merge(int x, int y){
int fx=find(x), fy=find(y);if(fx==fy) return 0;
if(siz[fx]<siz[fy]) swap(fx, fy);
rub.pb(fy);f[fy]=fx, siz[fx]+=siz[fy];
calc(fx, fy, 1);
return 1;
}
void reback(){
int fy=rub.back();rub.pop_back();
calc(f[fy], fy, -1), siz[f[fy]]-=siz[fy], f[fy]=fy;
}
int query(int x, int y){
x=find(x);
int i=0, s=0;for(; i<=bid[n]&&s+cnt[x][i]<y; ++i) s+=cnt[x][i];
if(i>bid[n]) return -1;
for(int j=L[i]; j<=R[i]; ++j) if(find(num[j])==x&&(++s)==y) return p[j];
return 0;
}
void dfs(int x){
bool flg=0;
if(Y[x]>0) flg=merge(X[x], Y[x]);
else if(Y[x]<0) ans[x]=query(X[x], -Y[x]);
for(auto v:G[x]) dfs(v);
if(flg) reback();
}
signed main(){
n=read(), m=read();
for(int i=1; i<=n; ++i)
a[i]=p[i]=read(), siz[f[i]=i]=1;
sort(p+1, p+n+1);
for(int i=1, pos; i<=n; ++i)
pos=lower_bound(p+1, p+n+1, a[i])-p,
a[i]=pos+(vis[pos]++), num[a[i]]=i;//, printf("%d ", a[i]);puts("");
for(int i=1, c=0; i<=n; i+=M, ++c)
for(int j=0; j<M&&i+j<=n; ++j)
L[c]=i, R[c]=i+j, bid[i+j]=c;
for(int i=1; i<=n; ++i) cnt[i][bid[a[i]]]=1;//printf("%d\n", bid[n]);
for(int i=1, pos=0; i<=m; ++i){
int op=read();
if(op==1) X[i]=read(), Y[i]=read(), G[pos].pb(i), pos=id[i]=i;
if(op==2) pos=id[i]=id[read()];
if(op==3) X[i]=read(), Y[i]=-read(), G[pos].pb(i), pos=id[i]=i;
}
dfs(0);
for(int i=1; i<=m; ++i) if(Y[i]<0) printf("%d\n", ans[i]);
return 0;
}