题意
初始序列 (P={1,2,dots ,n}),有 (m) 个操作,每次操作以 ((k,x)) 的形式给出,表示在上一次操作的结果的基础上进行 (x) 次以 (k) 为步长的约瑟夫环的跳跃操作。并依次将选中的数拿出,组成一个新的序列。求出最终得到的序列。
(1leq n,m leq 10^5,1leq n imes m leq 10^6,1leq k leq n,1leq x leq 10^9)
题目链接:https://ac.nowcoder.com/acm/contest/5671/J
分析:
每次进行操作形成新序列时,其实就相当于进行置换。因此,只要求出一次置换的排列,其它相同步长的跳跃,都是一样的。
对于当前选择的位置 (pos) ,假设当前原序列中还剩余 (num) 个数,那么下一个选择的数在由剩余数组成的排列中的位置为 ((pos-1+k-1)\%num+1)。而要求出剩余的数的个数和剩余序列中的第 (x) 个数的位置,都可以借助权值线段树来求。而考虑到置换满足结合律并且 (x) 较大,因此可以用快速幂处理。注意在求置换时,是对位置求。
复杂度:(O(nm·logn))
代码
#include <bits/stdc++.h>
using namespace std;
const int N=1e5+5;
int tree[N<<2];
int a[N],ans[N],c[N],d[N];
void pushup(int rt)
{
tree[rt]=tree[rt<<1]+tree[rt<<1|1];
}
void build(int l,int r,int rt)
{
tree[rt]=0;
if(l==r)
{
tree[rt]=1;
return;
}
int mid=(l+r)>>1;
build(l,mid,rt<<1);
build(mid+1,r,rt<<1|1);
pushup(rt);
}
void update(int l,int r,int pos,int rt)
{
if(l==r)
{
tree[rt]=0;
return;
}
int mid=(l+r)>>1;
if(pos<=mid) update(l,mid,pos,rt<<1);
else update(mid+1,r,pos,rt<<1|1);
pushup(rt);
}
int query(int l,int r,int L,int R,int rt)
{
if(L<=l&&r<=R)
return tree[rt];
int mid=(l+r)>>1,res=0;
if(L<=mid) res+=query(l,mid,L,R,rt<<1);
if(R>mid) res+=query(mid+1,r,L,R,rt<<1|1);
return res;
}
int ask(int l,int r,int k,int rt)
{
if(l==r) return l;
int tmp=tree[rt<<1],mid=(l+r)>>1;
if(k<=tmp) return ask(l,mid,k,rt<<1);
else return ask(mid+1,r,k-tmp,rt<<1|1);
}
void solve(int k,int n)
{
int cnt=0,p=k;
a[++cnt]=k;//对位置求
update(1,n,k,1);
while(++cnt<=n)
{
int s=query(1,n,1,p,1);
int x=(s+k-1)%tree[1]+1;
p=ask(1,n,x,1);
a[cnt]=p;//对位置求
update(1,n,p,1);
}
}
void change(int n,int x[],int y[])
{
for(int i=1;i<=n;i++)
d[i]=x[y[i]];
for(int i=1;i<=n;i++)
x[i]=d[i];
}
void power(int n,int b)
{
for(int i=1;i<=n;i++) c[i]=i;
while(b)
{
if(b&1) change(n,c,a);
change(n,a,a);
b>>=1;
}
}
int main()
{
int n,m,k,x;
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++) ans[i]=i;
for(int i=1;i<=m;i++)
{
scanf("%d%d",&k,&x);
build(1,n,1);
solve(k,n);
power(n,x);
change(n,ans,c);
}
for(int i=1;i<=n;i++)
printf("%d%c",ans[i],i==n?'
':' ');
return 0;
}