[构造] 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;
}
posted @ 2026-01-12 20:13  Zwi  阅读(3)  评论(0)    收藏  举报