H Permutation and Queries
题意:
给你一个
n
(
1
≤
n
≤
1
0
5
)
n(1≤n≤10^5)
n(1≤n≤105) 个数的排列 p,你需要维护以下两种操作:
1
x
y
1 x y
1xy :交换
p
x
p_x
px 和
p
y
p_y
py。
2
i
k
2 i k
2ik :令
i
=
p
i
i=p_i
i=pi,
k
k
k次后输出
i
i
i 。
操作数量小于等于
1
0
5
10^5
105。
思路:
首先分析题目,考虑 从
i
i
i 向
p
i
p_i
pi 连接一条边,因为数组是排列,所以每个点 只有一条出边和一条入边,那么构成的图 就是一个个有向环。
对于操作二,很容易地想到 倍增表,但是题目附带修改操作,对于 倍增表的修改 需要很高的复杂度。所以考虑另一种 数据结构 分块,维护从当前点出发,走
n
\sqrt n
n 步后 到达的点,那么对于操作二,只需要
n
\sqrt n
n次 就可以得到答案;接下来就是维护操作一了,对于操作一,交换两个数之后,可能两个环合并成一个,也可能一个环分裂成两个,对于图的变化,可以用 双向链表来实现;重要的是 图变化了之后,有些点 走
n
\sqrt n
n 步后 到达的点发生了变化,但是可以发现,发生了变化了的点,最多只有
x
x
x 和
y
y
y 的
n
\sqrt n
n个前驱结点,暴力修改即可。
详情见代码啦:
#include<cstdio>
#include<cstdlib>
#include<cmath>
#include<cstring>
#include<iostream>
#include<queue>
#include<stack>
#include<map>
#include<unordered_map>
#include<set>
#include<algorithm>
using namespace std;
#define ll long long
const ll mod=1e9+7;
const ll maxn=1e7+5;
const ll INF=0x3f3f3f3f;
ll qp(ll x,ll y){
ll ans=1;
while(y){
if(y%2)ans=ans*x%mod;x=x*x%mod;y/=2;}return ans;}
ll fmul(ll x,ll y){ //快速乘
ll tmp=(x*y-(ll)((long double)x/mod*y+1.0e-8)*mod);
return tmp<0?tmp+mod:tmp;
}
inline ll qread(){
ll s=0,w=1;char ch=getchar();
for(;!isdigit(ch);ch=getchar())if(ch=='-')w=-1;
for (;ch>='0'&&ch<='9';ch=getchar())s=(s<<1)+(s<<3)+(ch^48);
return (w==-1?-s:s);}
int n,m,q;
int to[100050][2];
int too[100050];
void link(int x){
int y=x;
for(int i=1;i<=m;i++)y=to[y][0];
for(int i=1;i<=m;i++){
too[x]=y;
x=to[x][1];
y=to[y][1];
}
}
void update(int x,int y){
int u=to[x][0];
int v=to[y][0];
to[x][0]=v;to[y][0]=u;
to[v][1]=x;to[u][1]=y;
link(x);
link(y);
}
int query(int x,int y){
int now=0;
while(now+m<=y){
x=too[x];
now+=m;
}
while(now<y){
now++;
x=to[x][0];
}
return x;
}
int main(){
scanf("%d%d",&n,&q);
m=sqrt(n);
for(int i=1;i<=n;i++){
scanf("%d",&to[i][0]);
}
for(int i=1;i<=n;i++){
to[to[i][0]][1]=i;
}
for(int i=1;i<=n;i++){
if(!too[i])link(i);
}
while(q--){
int op,x,y;
scanf("%d%d%d",&op,&x,&y);
if(op==1){
update(x,y);
}else {
printf("%d\n",query(x,y));
}
}
return 0;
}