题意:
给你n(1e3)个数,每个数都在[1,1e9]范围。 然后让你保持数的顺序不变,选出长度恰好为m(1<=m<=n)的单调上升子序列。 问你有多少种选择方案
/*
分析:
题目涉及到选择,我们不妨回归到一个很经典的DP模型——
用f[i][j]表示前i个数中选择了j个数的上升子序列的方案数。
这个状态的一个好处是,它把这个上升自序列的长度也包含了,就是j。
还有一个性质,我们如何查看一个数能否接在前面的序列后?其实只要知道之前上升子序列的最后一位就可以进行判定了。
鉴于这个,我们修改下f[i][j]的含义——
f[i][j]表示前i个数中选择了j个数,且最后一个数严格为a[i]时的上升子序列方案数。
这样就有状态转移f[i][j]=∑f[p][j-1],p∈[1,i-1]且a[p]<a[i]
因为我们可以严格保持i的升序,所以就只需要找到所有长度为j-1且尾节点a[p]<a[i]的子序列个数即可。
且因为for i for j已经使得复杂度变成O(n^2),所以这个操作要在log(n)级别的时间内完成。
而且这个操作设计到动态操作,于是我们想到树状数组(线段树也可)。
这样这道题的做法就很清晰了。
1,离散化
2,状态转移
for(int i=1;i<=n;i++)
{
//其实这个j的顺序并不重要,因为我们更新的尾节点长度为a[i],自己是无法延伸而来的
for(int j=top;j>=1;j--)
{
if(j==1)f[i][j]=1;
else add(f[i][j],cnt(j-1,a[i]-1));
add(j,a[i],f[i][j]);
}
add(ans,f[i][m]);
}
*/
#include<bits/stdc++.h>
using namespace std;
#define LL long long
#define N 1005
const int mod = 1000000007;
int a[N], dp[N][N], sum[N][N];
void modify(int i, int j, LL val)
{
for(;j<N; j+=j&-j)
{
sum[i][j] += val;
if(sum[i][j] >= mod) sum[i][j] %= mod;
}
return ;
}
LL getsum(int i, int j)
{
LL ret = 0;
for(; j>0; j-=j&-j)
{
ret += sum[i][j];
if(ret >= mod)ret %= mod;
}
return ret;
}
void solve(int kase)
{
int n, m, cnt = 1;
memset(dp, 0, sizeof(dp));
memset(sum, 0, sizeof(sum));
set<int> st;map<int, int> mp;
scanf("%d%d", &n, &m);
for(int i = 1; i <= n; i++)
{
scanf("%d", &a[i]);
st.insert(a[i]);
}
for(auto it = st.begin(); it != st.end(); it++) mp[*it] = cnt++;
for(int i = 1; i <= n; i++)
{
int lim = min(m, i);
for(int j = 1; j <= m; j++)
{
if(j == 1) dp[i][j] = 1;
else dp[i][j] += getsum(j-1, mp[a[i]]-1);
if(dp[i][j] >= mod) dp[i][j] %= mod;
modify(j, mp[a[i]], dp[i][j]);
}
}
LL ans = 0;
for(int i = 1; i <= n; i++)
{
ans += dp[i][m];
if(ans >= mod) ans %= mod;
}
printf("Case #%d: %d\n", kase, ans);
}
int main()
{
int t, kase=1;cin>>t;
while(t--)
{
solve(kase++);
}
return 0;
}