题意:给出一个长度为 n 的排列,初始序列为 1 , 2 , 3 … , n。有 m 次操作,每次操作表示为 ( k , x ) ,即进行 x 次 k-约瑟夫变换(一次k变换表示取出的顺序),求最终排列。
题解:线段树求约瑟夫变换+置换群的幂
参考的大佬博客
对于每一次操作,我们只要求出一次k-约瑟夫变换,x次的话就是该置换的x次幂,O(n)求一下即可。
如何求一次的k-约瑟夫变换呢?pos = (pos - 1 + k - 1) % cnt + 1;
求得取出元素在剩余序列的位置,然后在线段树中查询,查询的时候顺便更新一下。这样就是O(nlogn)。
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include<cstdio>
#include<string>
#include<cstring>
#include<algorithm>
#include<queue>
#include<stack>
#include<cmath>
#include<vector>
#include<fstream>
#include<set>
#include<map>
#include<sstream>
#include<iomanip>
#define ll long long
using namespace std;
const int MAXN = 1e5 + 5;
int sum[MAXN << 2];
struct Node {
int l, r;
int mid() { return (l + r) >> 1; }
} tree[MAXN << 2];
void build(int l, int r, int rt) {
tree[rt].l = l;
tree[rt].r = r;
if (l == r) {
sum[rt] = 1;
return;
}
int m = tree[rt].mid();
build(l, m, rt << 1);
build(m + 1, r, rt << 1 | 1);
sum[rt] = sum[rt << 1] + sum[rt << 1 | 1];
}
int query(int rt, int pos) {
sum[rt]--;
if (tree[rt].l == tree[rt].r) return tree[rt].l;
if (sum[rt << 1] >= pos) return query(rt << 1, pos);
else return query(rt << 1 | 1, pos - sum[rt << 1]);
}
int p[MAXN], temp[MAXN], ans[MAXN], n;
bool vis[MAXN];
void solve(int t) { //O(n)实现置换群p的t次幂
memset(vis, false, n + 5);
for (int i = 1; i <= n; i++) {
if (vis[i])
continue;
vector<int> circle;
int pos = i;
while (!vis[pos]) {
circle.push_back(pos);
vis[pos] = true;
pos = p[pos];
}
int sz = circle.size();
for (int i = 0; i < sz; i++)
temp[circle[i]] = ans[circle[(i + t) % sz]];
}
for (int i = 1; i <= n; i++) ans[i] = temp[i];
}
int m, k, x;
int main() {
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; i++) ans[i] = i;
while (m--) {
build(1, n, 1);
scanf("%d%d", &k, &x);
int pos = 1, cnt = n;
for (int i = 1; i <= n; i++) {
pos = (pos - 1 + k - 1) % cnt + 1; //取出元素在剩余序列的位置
p[i] = query(1, pos);
//cout << p[i] << endl;
cnt--;
}
solve(x);
}
for (int i = 1; i <= n; i++) printf("%d ", ans[i]);
return 0;
}