返回顶部

Harbour.Space Scholarship Contest 2021-2022 (open for everyone, rated, Div. 1 + Div. 2) E. Permutation Shift (思维)

  • 题意:有一组排列\(a=[1,2,3,...,n]\),可以将排列的所有元素向右移动\(k\)个位置,然后选任意两个元素交换位置,最多交换\(m\)次,现在给你操作完成后的序列,问你有多少种可能的\(k\)值,使得原排列能得到现在的序列.

  • 题解:将所有\(a[i]\)--,这样在每个元素移动\(k\)个位置后,得到\(a[i]=(i-k+n)\mod n\).那么我们就可以反推得到\(k\)的情况下序列\(p\)中有多少位置是合法的,即\(k=(i-a[i]+n)\mod n\),用桶\(cnt\)记录\(k\)每个\(k\)有多少合法的位置.因为移动\(k\)个位置后,最多交换\(m\)次,那么最多出现\(2*m\)个不合法的位置,所以\(cnt_k\ge n-2*m\).而\(m\le \frac{n}{3}\),\(cnt_k\ge \frac{n}{3}\).那么,在之前算\(k\)的合法位置的时候,一个合法的位置只能唯一对应一个\(k\),所以要满足\(cnt_k\ge \frac{n}{3}\),\(k\)最多只有三种情况.这样的话时间复杂度就能满足了,我们可以\(O(n)\)解决问题.

    这里可以记一个板子,求最小交换次数的时候,将现序列的值\(b_i\)和原序列\(a_i\)连边,然后求连通块数\(cnt\),最后\(n-cnt\)就是最小交换次数.

    int loop(vector<int> p,int n){
        vector<bool> vis(n);
        int res=0;
        rep(i,0,n-1){
            if(vis[i]) continue;
            int j=i;
            while(!vis[j]){
                vis[j]=true;
                j=p[j];
            }
            res++;
        }
        return n-res;
    }
    
  • 代码:

    #include <bits/stdc++.h>
    #define ll long long
    #define fi first
    #define se second
    #define pb push_back
    #define me memset
    #define rep(a,b,c) for(int a=b;a<=c;++a)
    #define per(a,b,c) for(int a=b;a>=c;--a)
    const int N = 1e6 + 10;
    const int mod = 1e9 + 7;
    const int INF = 0x3f3f3f3f;
    using namespace std;
    typedef pair<int,int> PII;
    typedef pair<ll,ll> PLL;
    ll gcd(ll a,ll b) {return b?gcd(b,a%b):a;}
    ll lcm(ll a,ll b) {return a/gcd(a,b)*b;}
     
    int loop(vector<int> p,int n){
        vector<bool> vis(n);
        int res=0;
        rep(i,0,n-1){
            if(vis[i]) continue;
            int j=i;
            while(!vis[j]){
                vis[j]=true;
                j=p[j];
            }
            res++;
        }
        return n-res;
    }
     
    bool check(int n,int m,int k,vector<int> p){
        vector<int> q;
        rep(i,k,n-1) q.pb(p[i]);
        rep(i,0,k-1) q.pb(p[i]);
        return loop(q,n)<=m;
    }
     
    int main() {
        ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
        int _;
        cin>>_;
        while(_--){
            int n,m;
            cin>>n>>m;
            vector<int> a(n);
            rep(i,0,n-1) cin>>a[i];
            //a[i]-1=(i-k+n) mod n
            vector<int> cnt(n); //记录k情况下,合法的位置数
            rep(i,0,n-1){
                a[i]--;
                cnt[(i-a[i]+n)%n]++; //反推出k,因为每个a[i]在位置i上只会对应一种k的合法情况
            }
            vector<int> ans;
            rep(i,0,n-1){    //枚举k
                if(cnt[i]>=n-2*m && check(n,m,i,a)){
                    ans.pb(i);
                }
            }
            cout<<(int)ans.size()<<' ';
            for(auto w:ans) cout<<w<<' ';
            cout<<'\n';
        }
     
     
        return 0;
    }
    
posted @ 2021-07-25 16:02  Rayotaku  阅读(54)  评论(0编辑  收藏  举报