The Battle of Chibi(树状数组+dp)

本文解析了HDU5542-TheBattleofChibi问题,通过离散化和树状数组优化动态规划,将时间复杂度从O(n^3)降低到O(n^2logn),并提供了完整的代码实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目链接:The Battle of Chibi

题意:问你序列中长度为k的上升子序列有多少个。

思路(转自博客:HDU 5542 - The Battle of Chibi - [离散化+树状数组优化DP]):

  1. 首先,不难想到应当假设 dp[i][j] 代表以 a[i] 为结尾的且长度为 j 的严格单增子序列的数目,那么自然地,状态转移就是 dp[i][j]=∑dp[k][j−1],其中 k 满足 1≤k<i 且 a[k]<a[i]。
    不过,题目是不可能这么简单的……这样纯暴力dp的话时间复杂度为 O(n3),超时。

  2. 考虑进行优化:
    先对 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]。

  3. 然后,既然要优化求前缀和的速度,不妨对 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;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值