题目链接:The Battle of Chibi
题意:问你序列中长度为k的上升子序列有多少个。
思路(转自博客:HDU 5542 - The Battle of Chibi - [离散化+树状数组优化DP]):
-
首先,不难想到应当假设 dp[i][j] 代表以 a[i] 为结尾的且长度为 j 的严格单增子序列的数目,那么自然地,状态转移就是 dp[i][j]=∑dp[k][j−1],其中 k 满足 1≤k<i 且 a[k]<a[i]。
不过,题目是不可能这么简单的……这样纯暴力dp的话时间复杂度为 O(n3),超时。 -
考虑进行优化:
先对 dp 数组进行改造,假设 dp[ai][j] 代表:在数字序列的 [1,i] 区间内,以数字 a[i] 为结尾的长度为 j 的严格单增子序列的数目,这样一来,原来的 a[i] 的范围在 [1,109],数组是开不下的。所以,需要对 a[1∼n] 先进行离散化,使得所有 a[i] 的范围均在 [1,n],这样数组就开的下了。自然而然地,状态转移方程就变成了 dp[ai][j]=dp[1][j−1]+dp[2][j−1]+⋯+dp[ai−1][j−1]。 -
然后,既然要优化求前缀和的速度,不妨对 dp[1∼n][1] 构造一个树状数组,对 dp[1∼n][2] 构造一个树状数组,⋯,对 dp[1∼n][m] 构造一个树状数组。这样一来,我要求 dp[1][j−1]+dp[2][j−1]+⋯+dp[ai−1][j−1] 这样一个前缀和就可以在 O(logn) 时间内完成。总时间复杂度变为 O(n2logn)。那么最后所求答案即为 dp[1][m]+dp[2][m]+⋯+dp[n][m]。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<cmath>
#include<algorithm>
#include<queue>
#include<stack>
#include<vector>
#include<map>
#include<set>
#define lowbit(x) x&(-x)
#define inf 0x3f3f3f3f
using namespace std;
typedef long long ll;
const ll N = 1010;
const ll mod = 1e9+7;
ll n, k;
ll a[N];
ll b[N];
ll dp[N][N];
void update(ll i, ll j, ll y)
{
y %= mod;
while(i <= n)
{
dp[i][j] += y;
dp[i][j] %= mod;
i += lowbit(i);
}
}
ll query(ll i, ll j)
{
ll ret = 0;
while(i > 0)
{
ret += dp[i][j];
ret %= mod;
i -= lowbit(i);
}
return ret;
}
int main()
{
ll t;
scanf("%lld", &t);
for(ll ca = 1; ca <= t; ca++)
{
memset(dp, 0, sizeof(dp));
scanf("%lld %lld", &n, &k);
for(ll i = 1; i <= n; i++)
{
scanf("%lld", &a[i]);
b[i] = a[i];
}
sort(b+1, b+n+1);
for(ll i = 1; i <= n; i++)
{
a[i] = lower_bound(b+1, b+1+n, a[i]) - b;
}
for(ll i = 1; i <= n; i++)
{
for(ll j = 1; j <= k; j++)
{
if(j == 1) update(a[i], 1, 1);
else update(a[i], j, query(a[i]-1, j-1));
}
}
printf("Case #%lld: %lld\n", ca, query(n, k));
}
return 0;
}