一碗递推水:排列

【问题描述】  
 
  将自然数 1 到 n 任意排列,然后在排列的每两个数之间根据他们的大小关系插入“ >”和“ <”。例如:对于 1..5 的一个排列: 3 2 4 1 5,可得到: 3 > 2 < 4 > 1 < 5,其中有两个“ >” 和2 个“ <”。 

  现在给出自然数 n, 问在自然数 1..n 的所有排列中,有多少个排列恰好有 k 个“ <”。 

  请你解答这个问题。
     
  【输入格式】  
 
  包含多组数据。第一行一个整数 T,表示有 T 组数据。
  每组数据的占一行,包含两个整数 n 和 k,它们之间用一个空格分开。
     
  【输出格式】  
 
  共 T 行, 每组数据输出一行,每行一个整数,表示对应输入的排列数,这个数如果很大,则需要输出 mod 1000000007 的结果。
     
  【输入样例】  
 
4
5 2
8 1
9 4
7 3
     
  【输出样例】  
 
66
247
156190
2416
     
  【数据范围】  
 
对于 30%的数据: n<=10
对于 100%的数据: k<n<=1000

分析:

 看数据k<=1000 n<=1000很容易能想到用动规

 本着求啥设啥的懒人原则

 设f i,j 为 放第i个数时满足j个“<”关系的方案数

 接下来是f i,j 的状态分析:

  对于一个新数i的放入  (第i个数是当前最大的数)

  那么考虑将i放入原先由 < 连接和 > 连接的两数之间

      情况1(注意:需要考虑最初位置,第一个数与位置0):

      插入新数i并不会改变原有的 < 关系

      那么这样的选择有: 原来的 j 个 加上 将i放在所有数之前的抉择

      也就是 t1=f i-1,j  * (j+1) ;

     情况2:(相似的,最后一个位置也应当考虑)

      插入新数i会新增一个 < 关系

      选择有:i-1-j  个的原有 > 关系(i个数提供 i-1 个关系其中j个为<)加上  放在最末的抉择

      也就是 t2= f i-1,j-1 * (i-j)

    最后 f i,j =t1 + t2;

/*
    f(i,j)  第i个数满足j个"<"关系的方式
     
*/
#include<bits/stdc++.h>
using namespace std;
const int mod=1000000007;
int t;
int n,k;
long long f[1005][1005];
void re()     //毕竟鬼知道T有多大,为了你我安全而打表
{
    memset(f,0,sizeof(f));
    f[0][0]=1;
    
    for(int i=1;i<=1000;i++)
    for(int j=min(i,1000);j>=0;j--)
    {
        f[i][j]=f[i-1][j]*(j+1)+f[i-1][j-1]*(i-j);
        f[i][j]%=mod;
    }
    
    return ;
}
void solve()
{
    scanf("%d%d",&n,&k);
    
    cout<<f[n][k]<<endl;
    
    return ;
}
int main(){
    re();
    scanf("%d",&t);    
    for(int i=1;i<=t;i++) 
      solve();
    return 0;
}

 

posted @ 2018-01-13 09:56  Wannabtl  阅读(209)  评论(0编辑  收藏  举报