叠积木
洛谷P2342
技术统计
难度 提高+/省选-
用时 30min
提交次数 3
unaccept 次数 1
ac次数 2
题意概括
我们首先将所有的积木想象为一个一个的栈,要求我们维护一下个操作
- 将两个栈合并
- 查询i号点在其所在的栈的高度( 到栈底有多少个元素 )
数据范围
1 < = p < = 1 0 5 1<=p<=10^5 1<=p<=105
解法、
知识点
- 并查集
- 加权并查集(嘤)
解法概括
用三个数组:head[ i ],cnt[ i ],fa[ i ],分别维护i号点所在的栈的栈顶元素、i号点到栈底的高度、i号点所在的栈的栈底元素。我们设i号点所在的栈为栈A,j号点(请先不要疑惑j号点是哪里来的,这其实就是设了一个未知数)所在的栈的为栈B。
head[i]的更新
当我们将栈A放在栈B上面的时候,显然栈A的栈顶变为栈B的栈顶,所以head[j]=head[i]。
cnt[i]的更新
我们使用一种类似于线段树中懒标记的思想:用的时候再更新。
但是我们不可能重新开其他数组记录所有的合并信息(好像可以,但会很浪费空间),所以我们就先只更新原来栈A的栈底k,让cnt[k]=cnt[head[j]]+1,等询问到的时候,我们用find函数找爸爸的特性来更新。
fa[i]的更新… …
就是普通并查集的更新了啊
坑点
- 在维护点x到栈底的距离的时候,可以在find函数中修改,但一定要加cnt[head[y]]
- 其实没必要维护一个sum[i]来记录以i为栈底的栈的大小,显然cnt[head[i]]==sum[i]
- 这里提一下用递归找父亲和while找父亲的区别:
一般while会跑得更快一些(实测,也可能是我的递归版本太渣),但是相对于递归版本,个人认为while不如递归版本灵活,我在打加权并查集的时候一般都用递归。
代码实现
#include<bits/stdc++.h>
#define maxn 30010
using namespace std;
int n;
int head[maxn],cnt[maxn],fa[maxn];
int find(int a)
{
if(a==fa[a])return a;
else
{
int fat=fa[a];
fa[a]=find(fat);
cnt[a]+=cnt[fat];
head[a]=head[fat];
return fa[a];
}
}
void merge(int x,int y)
{
fa[x]=y;
find(head[y]);
cnt[x]=cnt[head[y]]+1;
head[y]=head[x];
}
int main()
{
ios::sync_with_stdio(false);cin.tie(0);
cin>>n;
for(int i=1;i<=maxn;i++)fa[i]=head[i]=i;
for(int i=1;i<=n;i++)
{
char a;
int x,y;
cin>>a;
if(a=='M')
{
cin>>x>>y;
int fx=find(x),fy=find(y);
if(fx!=fy) merge(fx,fy);
}
else
{
cin>>x;
find(x);
cout<<cnt[x]<<endl;
}
}
return 0;
}