【jzoj5235】好的排列
题目描述
对于一个1->n的排列 ,定义A中的一个位置i是好的,当且仅当Ai-1>Ai 或者Ai+1>Ai。对于一个排列A,假如有不少于k个位置是好的,那么称A是一个好的排列。
现在有q个询问,每个询问给定n,k,问有多少排列是好的。答案对10^9+7取模。
输入
首先输入q。
接下来输入q个询问n,k 。
输出
输出q行,每行一个整数代表答案。
样例输入
8
4 3
6 4
10 7
20 14
50 40
100 72
1000 900
3000 2000
样例输出
8
448
1433856
868137807
908422882
609421284
150877522
216180189
数据范围
对于20%的数据,n<=10,q=1
对于40%的数据,n<=20,q=1
对于60%的数据,n<=100
对于100%的数据,n,k<=3000,q<=10000
思路
排列,k个位置有限制,方案数。这三个东西放在一起,首选动态规划啊。
状态?
排列dp的套路:
1、排了i-1个,最后一位是j。
2、1~i-1排好了,要排i。
第一个套路往往是第i位放什么数与i-1位有关时用。
第二种套路往往是把i插入一个排列。
当题目有k这种“有特殊限制”的参数时,往往可以把它放在dp参数里。
我们要有“看数据范围做题”的意识。排列,那肯定有一个排了几个数的参数i,再加上有几个位置是好的参数j,空间、时间已经达到n^2级别。那这里就只能用第二个套路了。
转移?
“定义A中的一个位置i是好的,当且仅当Ai-1>Ai 或者Ai+1>Ai。”
好,在前i-1排好的情况下我们插入了数i,至少现在数i肯定不是好的。那i两边的数呢,肯定是好的。
定义A中的一个位置i是坏的,当且仅当Ai-1<Ai且Ai+1<Ai。就像一个山峰,坏的数不可能连续。i两边的数要不都是好的,要不一好一坏。无论i放在一好一坏的地方,还是放在两个好的之间的地方,好的数不增加,坏的数有变化。
具体实现的时候我是逆向实现的,设f[i][j]表示1~i的排列有j个坏数的方案数,坏的数不连续,那么在有j个坏数的情况下就有2*j个一好一坏的位置。
方程:
f[i][j]=f[i-1][j]*2*j+f[i-1][j-1]*(i-2*(j-1)).
f[i-1][j]*2*j是放在一好一坏的情况,f[i-1][j-1]*(i-2*(j-1))是两个都好的情况。
代码
一行方程还需要代码?(算了边界看代码吧)
1 #include <iostream> 2 #include <cstdio> 3 #include <cstdlib> 4 #include <cmath> 5 #include <algorithm> 6 #include <cstring> 7 #include <string> 8 #include <queue> 9 using namespace std; 10 11 const long long mo=1000000007; 12 13 long long f[3050][3050]; 14 long long q[10050][2]; 15 long long n,m,i,j,k,t,ans; 16 17 int main() 18 { 19 cin>>t; 20 for (k=1;k<=t;k++) cin>>q[k][0]>>q[k][1]; 21 memset(f,0,sizeof(f)); 22 f[1][1]=1; 23 for (i=1;i<=3000;i++) f[i][0]=0; 24 for (i=2;i<=3000;i++) 25 for (j=1;j<=(i+1)/2;j++) 26 { 27 f[i][j]=((f[i-1][j]*2*j)%mo+(f[i-1][j-1]*(i-2*(j-1)))%mo)%mo; 28 } 29 for (k=1;k<=t;k++) 30 { 31 ans=0; 32 for (i=q[k][0]-q[k][1];i>=1;i--) ans=(ans+f[q[k][0]][i])%mo; 33 cout<<ans<<endl; 34 } 35 }