HDU5542(树状数组优化DP)

题意:

给你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;
}



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值