51Nod 1020 逆序排列

                                                                                            1020 逆序排列

 

在一个排列中,如果一对数的前后位置与大小顺序相反,即前面的数大于后面的数,那么它们就称为一个逆序。一个排列中逆序的总数就称为这个排列的逆序数。
如2 4 3 1中,2 1,4 3,4 1,3 1是逆序,逆序数是4。
 
1-n的全排列中,逆序数最小为0(正序),最大为n*(n-1) / 2(倒序)
给出2个数n和k,求1-n的全排列中,逆序数为k的排列有多少种?
例如:n = 4 k = 3。
 
1 2 3 4的排列中逆序为3的共有6个,分别是:
1 4 3 2
2 3 4 1
2 4 1 3
3 1 4 2
3 2 1 4
4 1 2 3
 
由于逆序排列的数量非常大,因此只需计算并输出该数 Mod 10^9 + 7的结果就可以了。
 

 

Input
第1行:一个数T,表示后面用作输入测试的数的数量。(1 <= T <= 10000)
第2 - T + 1行:每行2个数n,k。中间用空格分隔。(2 <= n <= 1000, 0 <= k <= 20000)
Output
共T行,对应逆序排列的数量 Mod (10^9 + 7)
Input示例
1
4 3
Output示例
6

dp+离线处理
我们很容易想到状态
dp[i][j] 代表在i个数的全排列中有j 的逆序数的方案数
下面我们来考虑这个状态的转移
显然将 i 插入 i-1 的排列中 不同的位置会产生不同的方案数
可能不产生逆序数 可能产生 1个 或 2个 或3个.....但最多能产生 i-1 个逆序数

于是 f[i][j]=f[i-1][j]+f[i-1][j-1]+f[i-1][j-2]+f[i-1][j-3]+f[i-1][j-4].........+f[i-1][j-(i-1)];
只有一个方程我们看不出怎么转移
我们 再找另一个方程 f[i][j-1];
f[i][j-1]=f[i-1][j-1]+f[i-1][j-1-1]+f[i-1][j-1-2]+f[i-1][j-1-2]...............f[i-1][j-1-(i-1)];

显然两个方程右边是有许多相同项的
我们用上式减去下式
得到 f[i][j]-f[i][j-1]=f[i-1][j]-f[i-1][j-1-(i-1)];
化简得到 f[i][j]=f[i][j-1]+f[i-1][j]-f[i-1][j-i];

于是就可以转移了

 1 #include <cstdio>
 2 #include <cctype>
 3 
 4 const int mod=1000000007;
 5 const int MAXN=20010;
 6 
 7 int T,n,m;
 8 
 9 int dp[1010][MAXN];
10 
11 inline void read(int&x) {
12     int f=1;register char c=getchar();
13     for(x=0;!isdigit(c);c=='-'&&(f=-1),c=getchar());
14     for(;isdigit(c);x=x*10+c-48,c=getchar());
15     x=x*f;
16 }
17 
18 int hh() {
19     read(T);
20     for(int i=1;i<=1000;++i) dp[i][0]=1;
21     for(int i=2;i<=1000;++i)
22       for(int j=1;j<=20000&&j<=i*(i-1)/2;++j) {
23           dp[i][j]=(dp[i][j-1]+dp[i-1][j])%mod;
24           if(j-i>=0) dp[i][j]=((dp[i][j]-dp[i-1][j-i])%mod+mod)%mod;
25       } 
26     while(T--) {
27         read(n);read(m); 
28         printf("%d\n",dp[n][m]);
29     }
30     return 0;
31 }
32 
33 int sb=hh();
34 int main(int argc,char**argv) {;}
代码

 



 

posted @ 2017-09-12 21:10  拿叉插猹哈  阅读(149)  评论(1编辑  收藏  举报