洛谷 P3672 小清新签到题 [DP 排列]
题意:给定自然数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=301, M=N*(N-1)/2; inline ll read(){ char c=getchar();ll x=0,f=1; while(c<'0'||c>'9'){if(c=='-')f=-1; c=getchar();} while(c>='0'&&c<='9'){x=x*10+c-'0'; c=getchar();} return x*f; } int n, x, m, a[N], vis[N]; ll f[N][M], k, s[2][M]; void dp(){ f[0][0]=1; int p=0; for(int i=0; i<=m; i++) s[p][i]=1; for(int i=1; i<=n; i++, p^=1) for(int j=0; j<=m; j++) { int l=max(0, j-(i-1)), r=j; ll _= l==0 ? s[p][r] : s[p][r]-s[p][l-1]; f[i][j]= _>k ? k+1 : _; s[p^1][j]=f[i][j]; if(j!=0) s[p^1][j]+= s[p^1][j-1]; } } 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>=1; i--) { ll cnt=0; for(int j=1; j<=n; j++) if(!vis[j]) { int c=j-1; for(int t=1; t<j; t++) c-= vis[t]; if(f[i-1][x-c] + cnt>= k) {a[n-i+1]=j; vis[j]=1; x-= c; k-= cnt; break;} cnt+= f[i-1][x-c]; } } for(int i=1; i<=n; i++) printf("%d ",a[i]); //printf("\n%lf", (double)clock()/CLOCKS_PER_SEC); }
Copyright:http://www.cnblogs.com/candy99/