题目大意是在一个长度为 nnn 的数字序列中找到长度为 mmm 的严格上升子序列的个数。
注意,要找的是子序列的个数,并不是子串的个数。
思路
动态规划 +++ 树状数组维护。
我们设 f(i,j)f(i,j)f(i,j) 表示以 iii 结尾,长度为 jjj 的严格上升子序列数,则:
-
当 jjj 为 111 时,f(i,j)=1f(i,j)=1f(i,j)=1
-
当 jjj 不为 111 时,f(k,j−1)=∑k=1,a[i]>a[k]i−1f(k,j-1)=\sum_{k=1,a[i]>a[k]}^{i-1}f(k,j−1)=∑k=1,a[i]>a[k]i−1
所以答案即为 ∑i=1nf(i,m)\sum_{i=1}^nf(i,m)∑i=1nf(i,m)
如果我们直接暴力枚举,将会有 O(n3)O(n^3)O(n3) 的时间复杂度,无法通过此题,那么,我们就需要树状数组来维护了。
我们可以先将数组离散化,然后再建 mmm 个树状数组,分别维护长度为 111 至 mmm 的严格上升子序列的方案数,用来优化时间复杂度,此时的时间复杂度为 O(Tnmlogn)O(Tnm \log n)O(Tnmlogn)。
看数据范围,有些变量还是需要开 long long 的,具体的实现看代码。
Code
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#define MAXN 100010
#define MOD 1000000007
using namespace std;
int n,m;
int lowbit(int x)
{
return x&(-x);//求出lowbit,即最低位
}
int T;
long long f[1010][1010],c[1010][1010];//开long long保险
struct node{
int a;
int b;
}a[MAXN];
bool cmp(node x,node y)
{
if(x.a!=y.a)
return x.a<y.a;//排序,便于离散化
return x.b>y.b;
}
inline void put(int x,int y,long long z)
{
//更新c数组
for(;x<=n;x+=lowbit(x))
c[x][y]=(c[x][y]+z)%MOD;
return;
}
long long get(int x,int y)
{
//询问第x位之前长度为y的严格上升子序列的总数
int S=0;
for(;x;x-=lowbit(x))
S=(c[x][y]+S)%MOD;//随时取模
return S;
}
int t;
inline void work()
{
t++;
memset(f,0,sizeof(f));
memset(c,0,sizeof(c));//多组数据,别忘了随时清空
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
scanf("%d",&a[i].a),a[i].b=i;//离散化
sort(a+1,a+1+n,cmp);
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++){
if(j==1)
f[i][j]=1;
////以i结尾,长度为1的严格上升子序列就只有它本身,所以方案为1
else
f[i][j]=get(a[i].b-1,j-1);
//比a[i]小,也就是k,且长度为j-1,就符合转移条件
put(a[i].b,j,f[i][j]);
}
long long ans=0;
for(int i=m;i<=n;i++)//小优化,m之前没有长为m的序列
ans=(ans+f[i][m])%MOD;//累加方案
printf("Case #%d: %lld\n",t,ans);
}
int main()
{
scanf("%d",&T);//T组数据
while(T--)
work();
return 0;
//完结撒花~~
}