题目链接: Permutation Shift
大致题意
你有初始长度为
n
n
n的序列
a
=
{
1
,
2
,
3
,
.
.
.
,
n
}
a = \{1, 2, 3, ..., n\}
a={1,2,3,...,n}.
你可以将整个序列右移
k
(
k
∈
[
0
,
n
−
1
]
)
k(k \in [0, n - 1])
k(k∈[0,n−1])位, 超出的部分会移动到序列开头. 随后有至多
m
(
m
∈
[
0
,
n
3
]
)
m(m \in [0, \frac{n}{3}])
m(m∈[0,3n])次交换操作, 可以选择序列中任意两个元素进行交换.
现给出序列 b b b, 问有多少个 k k k可以让序列 a a a变换为序列 b b b, 并且输出所有合法的 k k k.
解题思路
思维
首先考虑到, 我们可以暴力计算出序列
a
a
a右移
k
k
k位的序列情况, 然后我们与序列
b
b
b去进行匹配, 对于
a
i
=
b
i
a_i = b_i
ai=bi的位置无需交换, 否则我们需要进行一个找环操作, 需要对每个独立环内元素进行环大小-1次交换.
整体复杂度: O(n2).
考虑到优化, 由于 m m m的上限取值为 n 3 \frac n3 3n, 因此我们至多可以将 2 m 2m 2m个元素交换到正确的位置上.
因为每个环需要进行环大小-1次交换. 当每个环大小都是2时, 达到最优情况.
这说明, 此时至少需要有 n − 2 m n - 2m n−2m个元素已经在正确位置上, 因此最理想情况也需要有 n 3 \frac n3 3n个元素在正确位置上. 而对于右移后, 对于每个位置 a i = b i a_i = b_i ai=bi的情况, 仅会存在一个 k k k中. 因此至多会有3个k满足题意.
因此我们先可以预处理出 c o u [ ] cou[] cou[], 存储当 a a a序列右移 k k k后, 能匹配的位置数量, 然后对于每个 c o u [ k ] + 2 m ≥ n cou[k] + 2m \ge n cou[k]+2m≥n的情况, 我们进行暴力判断即可.
注: 代码中序列 a a a, b b b和解题思路中相反.
AC代码
#include <bits/stdc++.h>
#define rep(i, n) for (int i = 1; i <= (n); ++i)
using namespace std;
typedef long long ll;
const int N = 3E5 + 10;
bool vis[N];
int a[N], b[N], cou[N], mp[N];
int main()
{
int t; cin >> t;
while (t--) {
int n, m; scanf("%d %d", &n, &m);
rep(i, n) cou[i - 1] = 0;
rep(i, n) scanf("%d", &a[i]), cou[(i - a[i] + n) % n]++, mp[a[i]] = i;
vector<int> res;
for (int k = 0; k < n; ++k) {
if (cou[k] + 2 * m < n) continue;
rep(i, n) b[i] = (i - k + n) % n ? (i - k + n) % n : n, vis[i] = 0;
int need = 0;
rep(i, n) {
if (a[i] == b[i] or vis[i]) continue;
int cur = i, sz = 0;
while (!vis[cur]) {
vis[cur] = 1, sz++;
cur = mp[b[cur]];
}
need += sz - 1;
}
if (need <= m) res.push_back(k);
}
printf("%u ", res.size());
for (auto& op : res) printf("%d ", op); puts("");
}
return 0;
}