牛客小白月赛65 E题 题解
阅读原文时间:2023年10月16日阅读:1

原题链接

题意描述

构造一个\(1\)到\(n\)的排列,使得其中正好有\(k\)个二元组\((i, j)\)满足,\(1\le i\lt j\le n\) && \(a_i - a_j = 2^x(x\in N)\)

\((1\le n \le 10^6, 1\le k \le 10^9)\)

做法分析

首先我们可以发现,每个数,它在序列能构成有用二元组的只有比他大的,那也就是说我们找到了排列的二元组最多情况,也就是\(n\)到\(1\)按从大到小顺序排列。

那进一步想,每个数在序列中最大能对他有用的数(即能构成二元组的)有多少个呢,发现对于每个数\(i\),满足\(i+2^x\le n\)的最大的\(x\)再\(+1\)即为每个数的最大贡献,其实也就是\(\log_2( {n - i} )+ 1\)。

这样就有了构造方法,对于每个不是\(n\)(没有比\(n\)大的数了)的数\(i\),如果\(\log_2( {n - i} )+ 1\)\(\le k\),那就将\(k\)减去其贡献值,并把他放到最后。

举个例子:

输入:5 5

得到:3 4 5 2 1

从\(1\)开始,他的贡献最大是\(\log_2( {5 - 1} )+ 1 = 3\),可以与\(2,3,5\)形成二元组,小于\(k\),于是\(k\)减\(3\),标记一下\(1\)被放到后面了。

再到\(2\),贡献是\(\log_2( {5 - 2} )+ 1 = 2\),可以与\(3,4\)形成二元组,小于等于\(k\),于是\(k\)减\(2\),标记一下\(2\)被放到后面了,这时候\(k\)已经等于\(0\)了,即我们构造的排列已经符合要求。

这样应该就很清楚了,时间复杂度\(O(n)\)。

代码

#include <bits/stdc++.h>
using namespace std;

int n, k;
int flag1[1000010], flag2[1000010];
int lg[1000010];
int main()
{
    scanf("%d%d", &n, &k);
    for(int i = 2; i <= n; i++)
        lg[i] = lg[i >> 1] + 1;
    for(int i = 1; i <= n; i++)
        flag1[i] = 1;
    for(int i = 1; i < n; i++)
    {
        int cha = n - i;
        if(lg[cha] + 1 <= k)
        {
            flag1[i] = 0;
            flag2[i] = 1;
            k -= lg[cha] + 1;
        }
        if(k == 0)
            break;
    }
    if(k > 0)
    {
        printf("-1");
        return 0;
    }
    for(int i = 1; i <= n; i++)
        if(flag1[i])
            printf("%d ", i);
    for(int i = n; i >= 1; i--)
        if(flag2[i])
            printf("%d ", i);

    return 0;
}

手机扫一扫

移动阅读更方便

阿里云服务器
腾讯云服务器
七牛云服务器

你可能感兴趣的文章