1 条题解

  • 0
    @ 2025-11-26 21:05:45

    Written by @Yue_chen

    题意:

    给定一个长度 n 和一个 k ;

    我们必须构造一个长度为 n 的序列 a(序列的定义为 1~n 当且仅当出现一次),并且,该序列 |a[i] - i| >= k。

    并且使字典序尽可能小。

    思路:

    答案唯一,一眼贪心。

    当 k > n/2 时:

    显然数组中间的数无论如何也没法满足|a[i] - i| >= k,直接输出-1。

    当 k < n/2 时:

    我们根据贪心发现,当 n >= 4*k时,(n, k)可以转移为(n - k*2, k);

    例如:

    9 2                                5 2

    3 4 1 2 7 8 9 5 6     ->   7 8 9 5 6

    由于前2*k位满足贪心的最小字典序,k很小时,不论 n 多大时,前缀数组一定为 3 4 1 2。因此我们可以直接推出前缀固定的排列方式。

    在上一步的化简之后,n 总会 < 4*k:

    我们根据贪心方法手玩一下四个样例:

    8 3

    4 5 6 7 8 1 2 3

    9 3

    4 5 6 7 8 9 1 2 3

    10 3

    4 5 6 1 8 9 10 2 3 7

    11 3

    4 5 6 1 2 9 10 11 3 7 8

    我们发现 当 n <= 3*k时:

    将 [1 2 3 ... n-1 n] 循环左移k次,就是最终答案

    当 3*k < n < 4*k 时:

    出现了一种特殊的构造方式,我们尝试模拟一下这种构造方式;

    实际上是先把最后k个数前移k次,再把前k个数后移k次,如果位置撞了,就顺延到后面的位置上,直到找到空位为止。

    剩下的数依次从小到大找到一个空的位置。

    时间复杂度O(n)

    AC代码:

    #include<bits/stdc++.h>
    using namespace std;
    
    using i64 = long long;
    
    void solve() {
        int n, k; cin>>n>>k;
        
        vector<int> p(n+1);//答案数组
        
        int m = n/(2 * k);
        //初步判断
        if(m == 0) {
            cout<<"-1\n";
            return ;
        }
        
        //化简转移
        int j = 1;
        while(m > 1) {
            for(int i=j; i<j+k; ++i) {
                p[i] = i + k;
            }
            for(int i=j+k; i<j+2*k; ++i) {
                p[i] = i - k;
            }
            m--;
            j += 2*k;
        }
        
        //分讨构造
        int len = n-j+1;
        if(len > k*3) {
            //先放后k个
            for(int i=n-k,x=0; i>n-k-k; --i,x++) {
                p[i] = n-x;
            }
            //再放前k个
            for(int i=j+k,x=0; x<k and i<=n; i++) {
                if(!p[i]) {
                    p[i] = j+x; x++;
                }
            }
            //最后总小到大依次放
            for(int i=j,x=j+k; i<=n; ++i) {
                if(!p[i]) {
                    p[i] = x++;
                }
            }
        }else {
            //直接循环左移k次放
            for(int i=j; i<=n-k; ++i) {
                p[i] = i + k;
            }
            for(int i=n-k+1; i<=n; ++i) {
                p[i] = j + i - (n-k+1);
            }
        }
        
        for(int i=1; i<=n; ++i) {
            cout<<p[i]<<" \n"[i==n];
        }
    }
    
    signed main(void) {
        ios::sync_with_stdio(false); cin.tie(nullptr);
        
        int t=1; //cin>>t;
        while (t--) solve();
        return 0;
    }
    
    • 1

    信息

    ID
    117
    时间
    1000ms
    内存
    256MiB
    难度
    9
    标签
    递交数
    77
    已通过
    3
    上传者