[构造] P8376 [APIO2022] 排列
posted on 2025-05-13 10:46:36 | under | source
题意:构造一个长度不超过 \(90\) 的排列,满足恰有 \(k\) 个上升子序列(包括空序列)。\(k\le 10^{18}\)。
首先长为 \(n\) 的升序排列有 \(2^n-1\) 个非空上升子序列,所以考虑二进制拆位,一种最简单的构造方法是若干升序排列相连,满足前一个比后一个整体都要大。
手玩一下,可以得到一个更优的构造方法,先构造一排升序排列,然后对剩下的 \(k\) 进行拆位,\(2^n\) 就在倒数第 \(n\) 个元素前面放一个元素即可。下面的元素应当降序排列。如图:

那么序列长度就是 \(2\log k\) 的级别,接下来可能需要一些常数优化。
你注意到 \(\log 10^{18}\) 大概是 \(60\) 左右,\(90\) 恰好是它的 \(1.5\) 倍。上面的升序排列难以优化,所以对下面的降序排列动手。
你发现,假如下面存在两个相邻的点 \(2^{i+1},2^i\),且它们前面存在两个未被调整过的点,就可以去掉点 \(2^{i+1}\),将 \(2^i\) 的高度调整到前两个点的上方,这样是等价的。如图:

这样恰好折半了!可以通过。
实现的话用个栈存一下未被调整的点,每次检查栈顶两个点即可。
代码
#include<bits/stdc++.h>
using namespace std;
#define LL long long
const int N = 1e2 + 5;
int stk[N], st, h[N], H;
std::vector<int> construct_permutation(LL k)
{
int bt = 0; st = 0;
stack<int> up[N];
for(int i = 60; ~i; --i)
if((k >> i) & 1){
bt = i;
break;
}
k -= 1ll << bt;
for(int i = bt; ~i; --i){
h[i] = -1;
if((k >> i) & 1){
stk[++st] = i;
if(st >= 4 && stk[st - 1] == i + 1){
up[stk[st - 3]].push(i);
st -= 2;
}
}
}
H = 0;
for(int i = st; i; --i){
h[stk[i]] = ++H;
while(!up[stk[i]].empty()) h[up[stk[i]].top()] = ++H, up[stk[i]].pop();
}
vector<int> ans;
for(int i = bt; ~i; --i){
if(h[i] != -1) ans.push_back(h[i] - 1);
if(i) ans.push_back(++H - 1);
}
return ans;
}

浙公网安备 33010602011771号