Josephus Transform
题目描述:
给定长度为
的排列
初始
和
次操作。每次操作可以用
表示,代表执行
次
约瑟夫变换。请输出最后的排列。
约瑟夫变换表示:将排列
排成一个环,从第一位开始逐个数数,将数到
的元素删除,并添加到一个新的排列
中。然后继续从下一个数开始数数。重复上述操作,直到所有元素都被添加到
中,
就是结果。例如
在执行
约瑟夫变换后可以得到
输入描述:
第一行包含两个整数 , , 接下来 行每行包含两个整数 , , ,表示一项操作 ,
输出描述:
一行,打印 个整数。
样例:
样例输入1:
5 1
3 1
样例输出1:
3 1 5 2 4
样例输入2:
5 2
3 3
2 3
样例输出2:
1 2 3 4 5
思路:
置换群!
又是置换群!!
怎么还是置换群!!!
为什么牛客多校有那么多置换群!!!!
前几场牛客置换群相关题解:
第二场
第五场
手动模拟了一下,你就会发现变几次之后就回去了…不信你可以把题目给的样例手算试试看,直接就想到置换群…
第一时间想到暴力求,一看到
的取值范围就放弃了(赛场上K题一直卡着)
作为牛客多校的特色置换群,这题与前几场的置换群题目稍有不同,需要使用树状数组(或线段树)来维护。
如果上一个被取出来的数字是
,当前还剩
个数,那么下一个被选出来的数字应该是剩下数字的第
个,这时记录这个序列就需要树状数组(或线段树)来处理了,时间复杂度
。然后可以用类似于快速幂的方法来做x次k-约瑟夫变换,时间复杂度
处理方法:二分+树状数组+置换群
时间复杂度:
(呜呜呜写的好烂,欢迎
前来指正和提点)
线段树写法
:
#include<bits/stdc++.h>
using namespace std;
const int MAXN=1e5+5;
int n,q,k,x,sum[MAXN],a[MAXN],b[MAXN],c[MAXN],t,l,r;
int find(int x){
int ans=0;
for(int i=x;i;i-=(i&(-i)))
ans+=sum[i];
return ans;
}
void use(int mid){
for(int i=mid;i<=n;i+=(i&(-i)))
sum[i]++;
}
int main(){
scanf("%d%d",&n,&q);
for(int i=1;i<=n;i++)
a[i]=i;
while(q--){
scanf("%d%d",&k,&x);
for(int i=1;i<=n;i++) sum[i]=0;//memset会超时......只能手动清零
t=1;
for(int i=1;i<=n;i++){
t=(t+k-2)%(n-i+1)+1,l=1,r=n;
while(l<r){
int mid=l+r>>1;
if(mid-find(mid)>=t) r=mid;
else l=mid+1;
}//二分
b[i]=l;
use(l);
}
while(x){
if(x&1){
for(int i=1;i<=n;i++)
c[i]=a[b[i]];
for(int i=1;i<=n;i++)
a[i]=c[i];
}
for(int i=1;i<=n;i++)
c[i]=b[b[i]];
for(int i=1;i<=n;i++)
b[i]=c[i];
x>>=1;
}
}
for(int i=1;i<=n;i++)
printf("%d ",a[i]);
puts("");
}