-
题意
- 给你一个长度为 (n) 的排列 (P) (初始化为 (P = {1,2,...,n})) 和 (m) 次操作,每次操作是一对((k,x))表示对排列(P) 进行 (x) 次 (K- Josephus transform)。
-
解析
- 发现对 (P) 进行一次 (K- Josephus transform),实际上就是对 (P) 进行一次置换,设这个置换为 (T) ,进行(x) 次就是对 (P) 进行 (x) 次置换 (T) 。如果我们能得到置换 (T) ,那么进行 (x) 次就可以用快速幂 (nlogx) 实现。
- 现在我们要考虑如何得到置换 (T) ,得到置换 (T) 实际上就是要知道对排列 (P) 进行一次 (K- Josephus transform) 后的排列 (P')
- 令 (vis[i] = 1) 表示 (i) 没有被取出,(Sum[i]) 为 (vis[i]) 的前缀和 。
- 令上一次被取出的数是第 (pos) 个,当前还剩下 (cnt) 个数字,那么下一个被取出来的数是剩下的第 ((pos-1+k -1)\%cnt + 1)
- 对于 (n = 5,k = 3) 的一次变化如下图
-
-
对排列 ({1,2,3,4,5}) 进行一次 (3- Josephus transform) 发现每次的 (pos') 实际上就是原数组 (Sum[i] = pos') 的第一个位置( 被取出的数的就是 (i) ,因为排列是 ({1,2,3,4,5}) ,第 (i) 位置上的数就是本身)
-
如果用上述前缀和的形式来得到 (P') 复杂度就是 (n^2)
-
如果考虑用树状数组优化,采用二分判断当前位置的前缀和是否为 (pos') ( 记得是找第一个位置 ),因为树状数组求前缀的复杂度为 (logn) 所以整体复杂度就是 (nlognlogn) ~~~( 不知道会不会超时 ) ~~~
-
由于树状数组求一段区间的和是 (logn) ,我们还可以考虑用线段树来维护v
-
若你现在需要找到区间 ([L,R]) 前缀和为 (v) 的第一个位置,如果左子树的 (Sum) 比 (v) 大,那么你要找的就在左子树上,否则你就要找右子树中 (v-Sum) 的那个位置
-
int Query(int t,int v){ if( l(t) == r(t) ) return l(t); if( s(L) >= v ) return Query(L,v); else return Query(R,v-s(L)); }
-
再来考虑一下最后一步快速幂怎么写
-
我们知道一次置换是 (T) ,跟快速幂一样,(T^2,T^4...) 都只需要平方一下,那么如果 (x) 在二进制下第(i)位置上为 (1) ,再用对 (P) 进行一次 (T^{1<<i}) 的置换
-
代码
-
#include <bits/stdc++.h> #define L t<<1 #define R L|1 #define l(a) ST[a].l #define r(a) ST[a].r #define s(a) ST[a].sum using namespace std; typedef long long ll; typedef pair<int,int> pii; const int Maxn = 1e6+10; const int Inf = 0x7f7f7f7f; const int Mod = 1e9+7; const double eps = 1e-7; struct Segment_Tree{ int l,r,sum; }ST[Maxn << 2]; void Updata(int t){ s(t) = s(L) + s(R); } void Build(int t,int ll,int rr){ l(t) = ll, r(t) = rr; if( ll == rr ) { s(t) = 1; return ; } int mid = (ll + rr) >> 1; Build(L,ll,mid); Build(R,mid+1,rr); Updata(t); } void Change(int t,int pos,int v){ if( pos < l(t) || r(t) < pos ) return ; if( l(t) == pos && r(t) == pos ) { s(t) = v; return ; } Change(L,pos,v); Change(R,pos,v); Updata(t); } int Query(int t,int v){ if( l(t) == r(t) ) return l(t); if( s(L) >= v ) return Query(L,v); else return Query(R,v-s(L)); } int ch[Maxn],per[Maxn],tmp[Maxn]; void _2(int n){ for(int i=1;i<=n;i++) tmp[i] = ch[ ch[i] ]; for(int i=1;i<=n;i++) ch[i] = tmp[i]; } void change(int n){ for(int i=1;i<=n;i++) tmp[ch[i]] = per[i]; for(int i=1;i<=n;i++) per[i] = tmp[i]; } int main(){ int n,m; scanf("%d %d",&n,&m); for(int i=1;i<=n;i++) per[i] = i; for(int i=1,k,x;i<=m;i++) { scanf("%d %d",&k,&x); Build(1,1,n); int pos = 1, cnt = n; for(int i=1;i<=n;i++) { int Now_pos = (pos-1+k-1)%cnt + 1; pos = Now_pos, cnt--; Now_pos = Query(1,Now_pos); Change(1,Now_pos,0); tmp[i] = Now_pos, ch[i] = i; } for(int i=1;i<=n;i++) ch[tmp[i]] = i; while( x ) { if( x&1 ) change(n); _2(n); x >>= 1; } } for(int i=1;i<=n;i++) printf("%d ",per[i]); return 0; }
-