牛客小白月赛65 E题 题解
题意描述
构造一个\(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;
}