题目链接:http://acm.zjnu.edu.cn/CLanguage/contests/1154/problems/1003.html
题意:
开始时给你一个空栈编号为0,接下来有n(n<=300000)个操作,第i次操作有三个类型,a表示第一种,输入v表示把之前生成的栈v取过来在栈顶加入i,b是第二种,输入v表示生成新的栈是在栈v弹出顶上的元素后的栈,保证一定存在栈顶,输出的是栈v弹出的元素,c是第三种,输入v w,表示取栈v为新栈,同时比较新的栈和栈w有多少个相同的元素,输出数量。
做法:
看数据量就知道肯定不是模拟了..时间和容量都不够。
这道题是用lca做的,反正我是想不到,配合并查集。如果是c操作,得到的新栈和原来的栈就是一个栈,那么用并查集把这u和v并在一起,表示这两个元素是同一个,a操作表示生成了一条新的边,i就是v的儿子,b表示是从v弹出元素之后,那么就是把i和v的父亲进行连接,注意这里所有的v都是在fin(v)之后的,因为要找的是第一次连接的那个点,然后不断更新top。下面代码里有些了注释,不懂可以理解理解,我是理解了很久。。。
#include<bits/stdc++.h>
#define rep(i,a,b) for(int i=(int)a;i<=(int)b;i++)
using namespace std;
typedef long long ll;
const int maxn=300005;
int q,fa[maxn][20],ff[maxn],dep[maxn],top[maxn];
char op[5];
int fin(int x){
return ff[x]==x?x:ff[x]=fin(ff[x]);
}
void un(int x,int y){
int fx=fin(x),fy=fin(y);
if(fx!=fy){
ff[fx]=fy;
fin(fx);
}
}
void add(int u,int f){
//printf("from %d to %d \n",u,f);
dep[u]=dep[f]+1;
fa[u][0]=f;
for(int i=1;i<maxn;i++){
if(dep[u]<(1<<i)) break;
fa[u][i]=fa[fa[u][i-1]][i-1];
}
}
int LCA(int x,int y){
if(dep[x]<dep[y]) swap(x,y);
for(int i=19;i>=0;i--)
if(dep[fa[x][i]]>=dep[y])
x=fa[x][i];
if(x==y) return x;
for(int i=19;i>=0;i--)
if(fa[x][i]!=fa[y][i])
x=fa[x][i],y=fa[y][i];
return fa[x][0];
}
int main(){
memset(fa,-1,sizeof(fa));
scanf("%d",&q);
rep(i,0,q) ff[i]=i;
for(int i=1;i<=q;i++){
int v,w;
scanf("%s",op+1);
if(op[1]=='a'){
//将点i连接到a所在的集合上
//表示这个点是在a的基础上多加了一个点
//向下连接表示栈i是从u加了某一个值而来的
scanf("%d",&v);
int fv=fin(v);
add(i,fv);
top[i]=i;
}
else if(op[1]=='b'){
//因为要消除v这次加入的点
//所以要输出top[v]之后把它和v的父亲合并
//表示是同一个结点 即使加边 但因为会un所以没有影响
scanf("%d",&v);
v=fin(v);
int fv=fin(fa[v][0]);
top[i]=top[fv];
add(i,fv);
un(i,fv);
printf("%d\n",top[v]);
}
else {
//将栈i顶赋值为v的顶
//找到v的一个集合 将i和v合并 表示是同一类型
//虽然v是从i来 但是还是v的父亲加了top[v]的点来的
//所以还是要将点i连向v的父亲
scanf("%d%d",&v,&w);
v=fin(v),w=fin(w);
top[i]=top[v];
int fv=fin(fa[v][0]);
un(i,v);
add(i,fv);
int nowfai=fin(i);
//cout<<"lca = "<<LCA(nowfai,w)<<endl;
printf("%d\n",dep[LCA(nowfai,w)]);
}
}
return 0;
}