本题解同步更新于我的博客欢迎围观★,°:.☆( ̄▽ ̄)/$:.°★

题意描述

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

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

做法分析

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

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

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

举个例子:

输入:5 5 得到:3 4 5 2 1

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

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

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

点个赞再走呗/kel

代码

#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;
}