排序工作量之新任务(SHOI2001)
给出两个整数n和t,求n的全排列中逆序对数为t的个数,和逆序对数为t的字典序最小全排列。
首先第一个问题可以用dp解决,(f[i][j])表示前i个数,j个逆序对的序列数,那么(f[i][j]=f[i-1][j-k] (k<i)(kle j))。
易证明一个全排列,交换值差1的两个数,逆序对个数+或-1。同时可以推出逆序对数为t时的字典序最小全排列,一定一个严格上升,公差为1的序列,后面接上两个严格下降,公差为1的序列,并且后面两个序列衔接处差2。因此从后往前处理,不停交换当前数与后面的一个数即可。具体怎么实现请看代码。
#include <cstdio>
#include <algorithm>
using namespace std;
const int maxn=25;
int n, t;
long long f[maxn][maxn*maxn];
int a[maxn];
int main(){
scanf("%d%d", &n, &t);
if (t==0){
puts("1");
for (int i=1; i<=n; ++i) printf("%d ", i);
return 0; }
f[1][0]=f[2][0]=f[2][1]=1;
for (int i=3; i<=n; ++i)
for (int j=0; j<=i*(i-1)/2; ++j)
for (int k=0; k<i&&k<=j; ++k)
f[i][j]+=f[i-1][j-k];
printf("%lld
", f[n][t]);
for (int i=1; i<=n; ++i) a[i]=i;
for (int i=n-1; i>=1; --i){
for (int j=n; j>i; --j){
swap(a[i], a[j]);
if (!(--t)) break;
} if (!t) break;
}
for (int i=1; i<=n; ++i) printf("%d ", a[i]);
return 0;
}