洛谷 P3672 小清新签到题 [DP 排列]

时间:2023-03-08 21:19:56

传送门

题意:给定自然数n、k、x,你要求出第k小的长度为n的逆序对对数为x的1~n的排列

$n \le 300, k \le 10^13$


一下子想到hzc讲过的DP

从小到大插入,后插入不会对前插入造成影响,$f[i][j]$表示$1..n$排列$j$个逆序对的方案数,枚举插在哪里

然后从前向后选择满足要求的字典序最小的构造就行了

一开始没注意$DP$方程是$O(n^4)$的T了一次,以后一定要跑一下极限数据

加上前缀和优化

然后会爆long long,但我们只关心与k相比大小,所以$>k$变成$k+1$就行了

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
#include <ctime>
using namespace std;
typedef long long ll;
const int N=, M=N*(N-)/;
inline ll read(){
char c=getchar();ll x=,f=;
while(c<''||c>''){if(c=='-')f=-; c=getchar();}
while(c>=''&&c<=''){x=x*+c-''; c=getchar();}
return x*f;
} int n, x, m, a[N], vis[N];
ll f[N][M], k, s[][M];
void dp(){
f[][]=;
int p=;
for(int i=; i<=m; i++) s[p][i]=;
for(int i=; i<=n; i++, p^=)
for(int j=; j<=m; j++) {
int l=max(, j-(i-)), r=j;
ll _= l== ? s[p][r] : s[p][r]-s[p][l-];
f[i][j]= _>k ? k+ : _; s[p^][j]=f[i][j];
if(j!=) s[p^][j]+= s[p^][j-];
}
}
int main(){
//freopen("in","r",stdin);
n=read(); k=read(); x=read();
m=x;
dp();
//printf("\n%lf\n", (double)clock()/CLOCKS_PER_SEC);
for(int i=n; i>=; i--) {
ll cnt=;
for(int j=; j<=n; j++) if(!vis[j]) {
int c=j-;
for(int t=; t<j; t++) c-= vis[t];
if(f[i-][x-c] + cnt>= k) {a[n-i+]=j; vis[j]=; x-= c; k-= cnt; break;}
cnt+= f[i-][x-c];
}
}
for(int i=; i<=n; i++) printf("%d ",a[i]);
//printf("\n%lf", (double)clock()/CLOCKS_PER_SEC);
}