D e s c r p t i o n Descrption Descrption
有 T T T组询问,每次询问长度为 n n n,每个数的范围都在 [ 1 ∼ k ] [1\sim k] [1∼k], L C S LCS LCS(最长上升子序列,在本题中严格单调)的长度为 p + 1 p+1 p+1的合法方案数,答案对 1 0 9 + 7 10^9+7 109+7取模
T ≤ 10000 , p < n ≤ 100 , k ≤ 300 T\leq 10000,p<n\leq 100,k\leq 300 T≤10000,p<n≤100,k≤300
S o l u t i o n Solution Solution
这题是一道比较显然的 d p dp dp,我是在考场时就想出方程,然后并且成功拿到了30,后来预处理一个前缀和就 A A A了
设 f [ i ] [ j ] [ l ] f[i][j][l] f[i][j][l]表示长度为 i i i的序列, L C S LCS LCS的末尾数组为 j j j, L C S LCS LCS的长度为 l l l时的方案数
边界: f [ 1 ] [ i ] [ 1 ] = 1 f[1][i][1]=1 f[1][i][1]=1,表示长度为1的序列填 i i i这个数字 L C S LCS LCS的长度都为1,方案也只有1种
如果这一位填了 j j j, L C S LCS LCS长度不变,显然上一位是有 j j j种选择的,即填 [ j ∼ j + j − 1 ] [j\sim j+j-1] [j∼j+j−1], f [ i ] [ j ] [ l ] = f [ i − 1 ] [ j ] [ l ] × j f[i][j][l]=f[i-1][j][l]\times j f[i][j][l]=f[i−1][j][l]×j
如果这一位填了 j j j,导致 L C S LCS LCS长度发生了变化,那么这时我们就要枚举一个 o o o,表示上一次选的数字,此时 f [ i ] [ j ] [ l ] = ∑ f [ i − 1 ] [ o ] [ l − 1 ] f[i][j][l]=\sum f[i-1][o][l-1] f[i][j][l]=∑f[i−1][o][l−1]
回答时 a n s = ∑ i = p + 1 k f [ n ] [ i ] [ p + 1 ] ans=\sum_{i=p+1}^k f[n][i][p+1] ans=∑i=p+1kf[n][i][p+1]
这样子复杂度 O ( n 2 k 2 + T k ) O(n^2k^2+Tk) O(n2k2+Tk)
我们发现, o o o是一段连续的数字,也就是我们加的 f [ i − 1 ] [ o ] [ l − 1 ] f[i-1][o][l-1] f[i−1][o][l−1]是可以用前缀和表示的,这样我们的时间复杂度就跌到了 O ( n 2 k + T k ) O(n^2k+Tk) O(n2k+Tk)
C o d e Code Code
#include<cstdio>
#include<cctype>
#include<cstring>
#define ri int
#define WYC 1000000007
using namespace std;int t,n,k,p;
long long f[110][313][110],ans,sum[101][101][301];
inline int read()
{
char c;int d=1,f=0;
while(!isdigit(c=getchar())) if(c=='-') d=-1;f=(f<<3)+(f<<1)+c-48;
while(isdigit(c=getchar())) f=(f<<3)+(f<<1)+c-48;
return d*f;
}
signed main()
{
t=read();
for(ri i=1;i<=300;i++) f[1][i][1]=1,sum[1][1][i]=i;
for(ri i=2;i<=100;i++)
for(ri l=1;l<=i;l++)
for(ri j=l;j<=300;j++)
{
(f[i][j][l]+=f[i-1][j][l]*j)%=WYC;
(f[i][j][l]+=sum[i-1][l-1][j-1])%=WYC;
(sum[i][l][j]=sum[i][l][j-1]+f[i][j][l])%=WYC;
}
while(t--)
{
n=read();k=read();p=read();
ans=0;
for(ri i=p+1;i<=k;i++) (ans+=f[n][i][p+1])%=WYC;
printf("%lld\n",ans);
}
}