传送门
题意很简单:给定一个长度为N的序列A,求A有多少个长度为M的严格递增子序列。
先考虑暴力的dp.设dp[i][j]表示的是以i结尾长度为j的LIS的数量.转移方程:
dp[i][j] = sum{dp[k][j-1] 0 <= k < j && a[i] > a[k]}.
代码:
fir(i,1,n){
fir(j,1,min(i,m)){
fir(k,0,j-1){
if(a[i] > a[k]){
dp[i][j] += dp[k][j-1];
}
}
}
}
这个算法是n^3的,题目数据是1e3,过不去.考虑优化.
观察转移方程和最内层的循环可以发现的是,待选集合是随着i增大而增大的,这启发我们可以用某种数据结构去优化内层循环.小于a[i]的个数的和这个属性可以用树状数组或者线段树去储存,为了方便我们选择树状数组.
具体做法就是开一个二维的树状数组.c[i][j]第一维表示的是长度.第二维维护权值序列.因为有负数的原因,我们可以选择离散化.
复杂度(O(nmlogn))
代码:
#pragma GCC optimize(2)
#define LL long long
#define pq priority_queue
#define ULL unsigned long long
#define pb push_back
#define mem(a,x) memset(a,x,sizeof a)
#define pii pair<int,int>
#define fir(i,a,b) for(int i=a;i<=(int)b;++i)
#define afir(i,a,b) for(int i=(int)a;i>=b;--i)
#define ft first
#define vi vector<int>
#define sd second
#define ALL(a) a.begin(),a.end()
#define bug puts("-------")
#define mpr(a,b) make_pair(a,b)
#include <bits/stdc++.h>
using namespace std;
const int N = 1e3+10;
const int mod = 1e9+7;
inline void read(int &a){
int x = 0,f=1;char ch = getchar();
while(ch<'0'||ch>'9'){
if(ch=='-')f=-1;ch=getchar();}
while(ch<='9'&&ch>='0'){
x=x*10+ch-'0';ch=getchar();}
a = x*f;
}
int tt,t,n,m,a[N];
vi all;
LL dp[N][N],c[N][N];
LL ask(int len,int p){
LL res = 0;
for(;p;p-=p&-p) res = (res+c[len][p])%mod;
return res;
}
void add(int len,int p,int v){
for(;p<=tt;p+=p&-p) c[len][p] = (c[len][p] + v)%mod;
}
int getpos(int x){
return lower_bound(ALL(all),x) - all.begin();
}
int main(){
read(t);
fir(tot,1,t){
printf("Case #%d: ",tot);
read(n);read(m);
all.clear();
all.pb(-2e9);
all.pb(-2e9+1);
fir(i,1,n)
read(a[i]),all.pb(a[i]);
mem(dp,0);
mem(c,0);
dp[0][0] = 1;
sort(ALL(all));
all.erase(unique(ALL(all)),all.end());
tt = all.size();
fir(i,1,n) a[i] = getpos(a[i]);
add(0,getpos(-2e9+1),1);
fir(i,1,n){
fir(j,1,min(i,m)){
dp[i][j] = (dp[i][j]+ask(j-1,a[i]-1))%mod;
add(j,a[i],dp[i][j]);
}
}
LL ans = 0;
fir(i,m,n) ans = (ans+dp[i][m])%mod;
printf("%lld\n",ans);
}
return 0;
}
/*
dp[i][j] 表示以i结尾,长度为j的LIS有多少个.
dp[i][j] = sum{dp[k][j-1] 0 <= k < j && a[i] > a[k]}.
*/