可持久化数组到可持久化并查集
最水的文章,没有之一!主要是因为太简单了。
可持久化数组
实现功能
- 查询并复制历史版本数组
- 修改并复制历史版本数组
实现方法
具体可以见我的博客文章:主席树。
我们按照类似的思想(不需要构建权值线段树),将初始数组直接建成一个线段树就好了,然后修改类似。
- 问:为什么不直接用原数组,修改某一位置,直接连一个新的点呢?
答:因为这样达不到快速查询的要求。而要存储空间小,快速查询,我们只能在二者中取权衡,并使得时间也在可承受范围内。线段树就恰好满足这样的性质。
那么就很容易写出来了:
#include <cstdio>
#include <algorithm>
#define MAXN 1000010
using namespace std;
int a[40*MAXN], L[40*MAXN], R[40*MAXN], root[40*MAXN], w[MAXN];
int cnt, N, M;
inline void read(int &x)
{
x = 0;
int f = 1;
char ch = getchar();
for(; ch < '0' || ch > '9'; ch = getchar())
if(ch == '-')
{
f = -1;
ch = getchar();
break;
}
for(; ch >= '0' && ch <= '9'; ch = getchar())
x = (x<<3) + (x<<1) + ch - '0';
x *= f;
}
void Build(int &rt, int l, int r)
{
rt = ++cnt;
if(l == r)
{
a[rt] = w[l];///l代表了当前点编号,rt是它在持久数组上的位置
return;
}
int mid = l + r >> 1;
Build(L[rt], l, mid);
Build(R[rt], mid+1, r);
}
void Change(int ver, int &rt, int l, int r, int pos, int val)
{
rt = ++cnt;
L[rt] = L[ver];
R[rt] = R[ver];//别忘了,博主就被坑了
if(l == r)
{
a[rt] = val;
return;
}
int mid = l + r >> 1;
if(pos <= mid)
Change(L[ver], L[rt], l, mid, pos, val);
else
Change(R[ver], R[rt], mid+1, r, pos, val);
}
///查找编号rt点在持久数组的下标
int Query(int rt, int l, int r, int pos)
{
if(l == r)
return rt;
int mid = l + r >> 1;
if(pos <= mid)
return Query(L[rt], l, mid, pos);
else
return Query(R[rt], mid+1, r, pos);
}
int main()
{
int opt, v, val, pos, posa;
read(N);
read(M);
for(register int i = 1; i <= N; i += 1)
read(w[i]);
Build(root[0], 1, N);
for(register int i = 1; i <= M; i += 1)
{
read(v);
read(opt);
read(pos);
root[i] = root[v];
if(opt == 1)
{
read(val);
Change(root[v], root[i], 1, N, pos, val);
}
else
{
posa = Query(root[i], 1, N, pos);
printf("%d\n", a[posa]);
}
}
return 0;
}
洛谷测评,最慢测试点耗时:1094ms。
可持久化并查集
实现功能
- 合并集合
- 回到历史版本(所有集合)
- 询问是否同集合
实现方法
因为并查集是自底向上的,因此不能直接做类似开点的方法达到可持久化的目的,因此我们需要转化。
聪明的大家除了我都知道,转化的方法就是弄成可持久化数组。有人可能会问了:这怎么可能联系到的?其实并不难。
你只需要维护一下数组中每个节点的父节点就差不多了(仅仅是差不多而已),注意,这里不能路径压缩,不然会乱套的(比如回到历史版本的时候你根本搞不清哪些节点该跟谁走,跟错了爸爸还得了)。但是不进行路径压缩的话,怎么办呢?不会超时间吗?这有办法,就是将深度小的集合(其实差不多是树了)在合并的时候合并到深度大的里面去,可以将树的深度维持在接近对数级别的范围。
那么我们剩下的工作就是和可持久化数组差不多了,代码也比较好懂,大家看代码读细节吧233。
#include <cstdio>
#include <algorithm>
#define MAXN 100010
using namespace std;
int fa[30*MAXN], L[30*MAXN], R[30*MAXN], de[30*MAXN], root[30*MAXN];
int cnt, N, M;
inline void read(int &x)
{
x = 0;
char ch = getchar();
for(; ch < '0' || ch > '9'; ch = getchar());
for(; ch >= '0' && ch <= '9'; ch = getchar())
x = (x<<3) + (x<<1) + ch - '0';
}
void Build(int &rt, int l, int r)
{
rt = ++cnt;
if(l == r)
{
fa[rt] = l;///l代表了当前点编号,rt是它在持久数组上的位置
return;
}
int mid = l + r >> 1;
Build(L[rt], l, mid);
Build(R[rt], mid+1, r);
}
void Merge(int last, int &rt, int l, int r, int pos, int Fa)
{
rt = ++cnt;
L[rt] = L[last];
R[rt] = R[last];
if(l == r)
{
fa[rt] = Fa;
de[rt] = de[last];
return;
}
int mid = l + r >> 1;
if(pos <= mid)
Merge(L[last], L[rt], l, mid, pos, Fa);
else
Merge(R[last], R[rt], mid+1, r, pos, Fa);
}
void Update(int rt, int l, int r, int pos)
{
if(l == r)
{
de[rt] += 1;
return;
}
int mid = l + r >> 1;
if(pos <= mid)
Update(L[rt], l, mid, pos);
else
Update(R[rt], mid+1, r, pos);
}
///查找编号rt点在持久数组的下标
int Query(int rt, int l, int r, int pos)
{
if(l == r)
return rt;
int mid = l + r >> 1;
if(pos <= mid)
return Query(L[rt], l, mid, pos);
else
return Query(R[rt], mid+1, r, pos);
}
int Find(int rt, int pos)
{
int v = Query(rt, 1, N, pos);
if(fa[v] == pos)///都是指下标的
return v;
return Find(rt, fa[v]);
}
int main()
{
int opt, x, y, posx, posy;
read(N);
read(M);
Build(root[0], 1, N);
for(register int i = 1; i <= M; i += 1)
{
read(opt);
read(x);
if(opt == 1)
{
read(y);
root[i] = root[i-1];
posx = Find(root[i], x);
posy = Find(root[i], y);
if(fa[posx] != fa[posy])
{
if(de[posx] > de[posy])
swap(posx, posy);
Merge(root[i-1], root[i], 1, N, fa[posx], fa[posy]);
if(de[posx] == de[posy])
Update(root[i], 1, N, fa[posy]);
}
}
else if(opt == 2)
root[i] = root[x];
else
{
read(y);
root[i] = root[i-1];
posx = Find(root[i], x);
posy = Find(root[i], y);
if(fa[posx] == fa[posy])
putchar('1'), putchar('\n');
else
putchar('0'), putchar('\n');
}
}
return 0;
}
洛谷测评,最慢测试点耗时(不开O2):264ms。
参考文献
- pengym. 题解 可持久化并查集. https://www.luogu.org/problemnew/solution/P3402, 2018-08-08
写在最后
因为题目真不难,所以博主偷偷懒就写了这么点,所以各位抱歉啦。不过我相信大家看通俗易懂的代码还是看得懂的吧(逃)。
感谢参考文献中提到的文献的帮助。
大多数内容为个人智力成果,如需转载,请注明出处,禁止作商业用途传播。
最后,感谢各位的阅读。