也许我dp太水了,所以在noip前要放弃fft,学好图论动规和爆搜,然后找到这题,依然懵逼。
题面
题意:给定自然数n、k、x,求第k小的,长度为n的,逆序对对数为x的,1~n的排列。
先考虑一个弱鸡问题:如何求出第k个排列。
我们枚举第一位i,剩下的排列数为(n-1)!,若(n-1)!<k,则i太小了,k减去(n-1)!,否则第一位就是i。由于排列只和数的相对大小有关,剩下的n-1个数可看成子问题。
即使有了逆序对的限制,我们依然这样想。枚举第一位i,i产生的逆序对数为(i-1),剩下x-(i-1)个逆序对,然后很自然的设出f[i][j]为1~i排列,逆序对为j的方案数。就可以以f来判断i是不是小了。
然后f也很容易求,同样是枚举第一位i,同样产生(i-1)个逆序对,有
f[i][j]=∑k=max(0,j−i+1)jf[i−1][k]
显然可以前缀和优化。
这题还有一些坑点,比如f会爆longlong,可用k+1代替,但前缀和数组不可以。
#include <iostream>
#include <fstream>
#include <algorithm>
#include <cmath>
#include <ctime>
#include <cstdio>
#include <cstdlib>
#include <cstring>
using namespace std;
#define mmst(a, b) memset(a, b, sizeof(a))
#define mmcp(a, b) memcpy(a, b, sizeof(b))
typedef long long LL;
const LL oo=1e13+1;
LL k,f[301][45003],sum[2][45003];
int n,x;
int ans[301],a[301];
bool vis[301];
void work(int o)
{
if(!o)
return;
for(int i=1;i<=o;i++)
{
if(f[o-1][x-i+1]<k)
k-=f[o-1][x-i+1];
else
{
ans[o]=a[i];
x=x-i+1;
int cnt=0;
vis[a[i]]=1;
for(int j=1;j<=n;j++)
if(!vis[j])
a[++cnt]=j;
work(o-1);
return;
}
}
}
int main()
{
cin>>n>>k>>x;
f[0][0]=1ll;
int nn=(n*n-n)/2;
for(int i=1;i<=n;i++)
{
int mx=(i*i-i)/2;
f[i][0]=1ll;
for(int j=1;j<=mx;j++)
{
f[i][j]=sum[(i&1)^1][j+1]-sum[(i&1)^1][max(j-i+1,0)];
if(f[i][j]>oo)
f[i][j]=oo;
}
for(int j=0;j<=nn;j++)
sum[i&1][j+1]=sum[i&1][j]+f[i][j];
a[i]=i;
}
work(n);
for(int i=n;i>=1;i--)
cout<<ans[i]<<" ";
cout<<endl;
return 0;
}