cf1553 E. Permutation Shift
题意:
初始有一个从小到大的排列 \(1,2,3,\cdots ,n\),接下来先把整个排列循环右移 \(k\) 位;再操作不超过 \(m\) 次,每次交换两个数。
给定 \(n,m\) 和目标排列 \(P\),求能得到 \(P\) 的 \(k\) 的所有可能取值。
\(3\le n\le 3e5,1\le m\le \frac n3\)
思路:
设循环右移 \(k\) 位后,有 \(x_k\) 个数恰在目标位置
注意到 \(m\) 次 swap 最多改变 \(2m\) 个数的位置,所以必须 \(2m\ge n-x_k\)
而 \(m\le \frac n3\),所以 \(x_k\ge \frac n3\)
\(x_k\) 是怎么算的呢?对某个数字 \(i\),设它的目标位置为 \(p_i\),那么 \(k=(p_i-i) \mod n\) 是使 \(i\) 右移到目标位置的唯一的 \(k\),所以可以 \(O(n)\) 算出所有的 \(x_k\),而且 \(\sum x_k=n\)
所以合法的 \(k\) 不超过 \(3\) 个!对每个 \(x_k\ge \frac n3\) 的 \(k\),线性计算即可。计算方法是 \(n-\) 环数
int n, m, p[N];
int cal(int k, bool ou = 0) {
vector<int> a; a.pb(0);
for(int i = n - k + 1; i <= n; i++) a.pb(i);
for(int i = 1; a.size() < n + 1; i++) a.pb(i);
int ans = n; vector<bool> vis(n+1, 0);
for(int i = 1; i <= n; i++) {
if(vis[a[i]]) continue; vis[a[i]] = 1;
for(int j = a[i]; p[j] != i; vis[j = a[p[j]]] = 1) ; //找环
ans--;
}
return ans;
}
void sol() {
cin >> n >> m;
for(int i = 1, t; i <= n; i++) cin >> t, p[t] = i;
vector<int> x(n, 0); for(int i = 1; i <= n; i++)
x[(p[i] - i + n) % n]++; //求x
vector<int> ans; for(int k = 0; k < n; k++)
if(x[k] >= n/3 && cal(k) <= m) ans.pb(k);
cout << ans.size() << ' ';
for(int k : ans) cout << k << ' ';
cout << endl;
}