2431: [HAOI2009]逆序对数列
Description
对于一个数列{ai},如果有i<j且ai>aj,那么我们称ai与aj为一对逆序对数。若对于任意一个由1~n自然数组成的
数列,可以很容易求出有多少个逆序对数。那么逆序对数为k的这样自然数数列到底有多少个?
Input
第一行为两个整数n,k。
Output
写入一个整数,表示符合条件的数列个数,由于这个数可能很大,你只需输出该数对10000求余数后的结果。
Sample Input
4 1
Sample Output
3
样例说明:
下列3个数列逆序对数都为1;分别是1 2 4 3 ;1 3 2 4 ;2 1 3 4;
100%的数据 n<=1000,k<=1000
样例说明:
下列3个数列逆序对数都为1;分别是1 2 4 3 ;1 3 2 4 ;2 1 3 4;
100%的数据 n<=1000,k<=1000
题解:(dp递推+优化)
稍微认真思考一下:
假设给你若干个位置,如果把n个数从小到大一个个放进去(可以插入到放好的位置当中),对于当前放入的第i个数来说,它一定比前i-1个数都要大,所以当它在前i-1个位置中选择时,对于选择的任意一个位置都是有后效性的。
定义f[i][j]表示用第i个数填(前i-1个位置)了之后,产生j对逆序对的方案数;
那么这时候我们发现,前i-1个数的方案数我们已经得出了,那么对于当前所要求的j对逆序对,我们可以直接从前面继承。每个数i要填时,后效性最少为0(填在最后),最大为i-1(最前面)。为0时我们就可以从f[i-1][j]继承,那么不难发现其他位置比如说填在第i-2个数的后面,就从f[i-1][j-1]继承。
所以方程:f[i][j]=f[i][j]=∑f[i-1][j-k](0<=k<i)
其实这样直接做会超时ORZ
用个前缀和优化一下就好啦,时间复杂度降为O(n^2)...
#include<cstdio> #include<cstring> #include<cstdlib> #include<cmath> #include<algorithm> #define mod 10000 using namespace std; inline int read() { int f=1,x=0;char ch; while(ch<'0' || ch>'9'){if(ch=='-')f=-1;ch=getchar();} while(ch>='0' && ch<='9'){x=x*10+ch-'0';ch=getchar();} return f*x; } int n,k; int f[1100][1100];//表示用第i个数填(前i-1个位置)了之后,产生j对逆序对的方案数; int main() { n=read();k=read(); memset(f,0,sizeof(f)); f[0][0]=1; for(int i=1;i<=n;i++)f[i][0]=1;//产生0个逆序对的方案数仅有一种:上升序列 int sum=0; for(int i=1;i<=n;i++) { sum=f[i-1][0]; for(int j=1;j<=k;j++) { if(j-i>=0)sum-=f[i-1][j-i]; sum+=f[i-1][j]; f[i][j]+=sum; f[i][j]+=mod; if(f[i][j]>=mod)f[i][j]%=mod; } } printf("%d ",f[n][k]);//输出填完n个数之后产生k个逆序对的方案数 return 0; }