题解:CF2040C Ordered Permutations
CF2040C 题解
题面
题面
定义一个序列 \(a\) 的权值为 \(\begin{aligned}\sum_{1\leqslant l\leqslant r\leqslant n} \min(a_l,a_{l+1},\cdots,a_r)\end{aligned}\),给定 \(n\) 和 \(k\),问长度为 \(n\) 的排列中权值最大的所有排列中字典序排第 \(k\) 大的排列,若没有则输出 -1。
思路
首先先分析一个数列的权值的求法,单看题目中给的式子有点难想什么时候是最大的,考虑分析排列中每个数字会造成的贡献,设 \(i\) 的位置为 \(p_i\),\(i\) 左边第一个比 \(i\) 小的数的位置为 \(l_i\),右边第一个比 \(i\) 小的数的位置为 \(r_i\),则 \(i\) 对序列的贡献为 \(i\times(p_i-l_i)\times(r_i-p_i)\)。
接下来考虑什么时候序列的权值会取到最大值,首先肯定要确保大的数尽可能的被多选,而对于一个序列的权值最大就为 \(\begin{aligned}\sum_{i=1}^{n} i\times(n-i+1)\end{aligned}\)。
接下来考虑如何才会构成这种权值最大的情况,其实也很简单,首先我们知道对于一个数 \(i\) 他的贡献为 \(i\times(n-i+1)\),所以 \((p_i-l_i)\times(r_i-p_i)=n-i+1\)。
容易想到(或者说暴力打个表),权值最大的排列的构造方式是从小到大放入每个数,每次放入一个数只能放在当前序列的最左边或者最右边,这样就可以满足每个数字的贡献。
所以权值最大的排列数为 \(2^{n-1}\),所以若 \(k>2^{n-1}\),则无解。
最后考虑字典序第 \(k\) 大的排列,因为数字是从小到达放的,每个数字都有放左边或放右边两种选择,所以放入 \(i\) 后还有 \(2^{n-i-1}\) 种放法,即只要用 \(k\) 与 \(2^{n-i+1}\) 比较就可以决定当前 \(i\) 是放最左端还是最右端,具体的有如下。
- 若 \(k<=2^{n-i+1}\),则 \(i\) 放左端。
- 若 \(k>2^{n-i+1}\),则 \(i\) 放右端,并将 \(k\) 减去 \(2^{n-i+1}\)。
代码
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#define ll long long
using namespace std;
const int MN=2e5+5;
ll n,k,ans[MN];
void write(ll n){if(n<0){putchar('-');write(-n);return;}if(n>9)write(n/10);putchar(n%10+'0');}
ll read(){ll x=0,f=1;char ch=getchar();while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}return x*f;}
ll ksm(ll a, ll b){ll res=1;while(b){if(b&1)res=res*a;a=a*a;b>>=1;}return res;}
void solve(){
for(int i=1; i<=n; i++) ans[i]=0;
n=read();k=read();
if(n<=40&&k>ksm(2,n-1)){write(-1);putchar('\n');return;}
ll l=1,r=n,op=0;
for(int i=1,j=n-2; i<n; i++,j--){
if(j>40){ans[l++]=i;continue;}//作者怕爆 long long
if(k>ksm(2,j)){
ans[r--]=i;
k-=ksm(2,j);
}
else{ans[l++]=i;}
}
ans[l]=n;
for(int i=1; i<=n; i++) write(ans[i]),putchar(' ');putchar('\n');
}
int main(){
ll T=read();while(T--)solve();
return 0;
}

浙公网安备 33010602011771号