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;
}
posted @ 2022-06-05 16:11  Bellala  阅读(31)  评论(0)    收藏  举报