The Battle of Chibi(树状数组优化DP)

题目链接:
https://vjudge.net/contest/390155#problem/M

题目大意:
给你一个长度为n的数组,问你这个数组中有多少种长度为m的递增序列,输出种类数。
思路:
这道题的状态转移方程很好想,我们用 d p [ i ] [ j ] dp[i][j] dp[i][j]表示以第 j j j位的数结尾,递增序列长度为 i i i的种类数。
那么转移方程就可以写成:
d p [ i ] [ j ] = d p [ i ] [ j ] + d p [ i − 1 ] [ k ] , ( a [ k ] < a [ j ] ) dp[i][j]=dp[i][j]+dp[i-1][k],(a[k]<a[j]) dp[i][j]=dp[i][j]+dp[i1][k],(a[k]<a[j])

/*
 * @沉着,冷静!: 噗,这你都信!
 * @LastEditors: HANGNAG
 * @LastEditTime: 2020-09-16 17:21:16
 * @FilePath: \ACM_vscode\DP\DP.cpp
 */
#include <algorithm>
#include <iostream>
#include <map>
#include <math.h>
#include <queue>
#include <set>
#include <stack>
#include <stdio.h>
#include <string.h>
#include <string>
#include <vector>
#define emplace_back push_back
#define pb push_back
using namespace std;
typedef long long LL;
const int mod = 1e9 + 7;
const double eps = 1e-6;
const int inf = 0x3f3f3f3f;
const int N = 1e3 + 10;
int dp[N][N];
int a[N];
int main()
{
    int t;
    scanf("%d", &t);
    int tot = 1;
    while (t--)
    {
        int n, m;
        memset(dp, 0, sizeof(dp));
        scanf("%d%d", &n, &m);
        for (int i = 1; i <= n; i++)
        {
            dp[i][1] = 1;
        }
        for (int i = 1; i <= n; i++)
        {
            scanf("%d", &a[i]);
        }
        for (int i = 2; i <= n; i++)
        {
            for (int j = 2; j <= i; j++)
            {
                for (int k = 1; k < i; k++)
                {
                    if (a[i] > a[k])
                    {
                        dp[i][j] += dp[k][j - 1];
                    }
                    dp[i][j] %= mod;
                }
            }
        }
        int ans = 0;
        for (int i = m; i <= n; i++)
        {
            ans += dp[i][m];
            //printf("%d %d %d**\n", i, m, dp[i][m]);
        }
        ans %= mod;
        printf("Case #%d: %d\n", tot++, ans);
    }
    return 0;
}

很显然这种朴素的写法的复杂度是 O ( n 3 ) O(n^3) O(n3)这道题过不了。
但是我们可以发现它每次加的都是比当前小的数的状态,那么我们可以把这个数组每个数的前缀记录,每次加上它的前缀。(如果现在判断到第j个数,那么我们先找到a[j]排第几个,假设排b[j],这里的前缀是指把这个数组排序后,dp[i][j]就加上数组里到b[j]-1的前缀和)
这里我们要想实现对一个数组进行修改和前缀查询,可以用线段树和树状数组实现,(树状数组更简单一些)。因为这里a数组范围比较大,但是数量不多,所以我们可以离散化一下a数组。
AC代码:

/*
 * @沉着,冷静!: 噗,这你都信!
 * @LastEditors: HANGNAG
 * @LastEditTime: 2020-09-16 21:09:20
 * @FilePath: \ACM_vscode\DP\DP.cpp
 */
#include<algorithm>
#include<string.h>
#include<iostream>
#include<stdio.h>
#include<string>
#include<math.h>
#include<vector>
#include<queue>
#include<stack>
#include<map>
#include<set>
#define emplace_back push_back
#define pb push_back
using namespace std;
typedef long long LL;
const LL mod = 1e9 + 7;
const double eps = 1e-6;
const LL inf = 0x3f3f3f3f;
const LL N = 1e3 + 10;
LL dp[N][N];
LL a[N], b[N], c[N];
LL n, m;
LL lowbit(LL x)
{
    return x & (-x);
}
LL sum(LL x)
{
    LL num = 0;
    while(x)
    {
        num += c[x];
        num %= mod;
        x -= lowbit(x);
    }
    return num;
}
void add(LL x,LL y)
{
    while(x<=n)
    {
        c[x] += y;
        c[x] %= mod;
        x+=lowbit(x);
    }
}
int main()
{
    LL t;
    scanf("%lld", &t);
    LL tot = 1;
    while(t--)
    {
        memset(dp, 0, sizeof(dp));
        scanf("%lld%lld", &n, &m);
        for (LL i = 1; i <=n;i++)
        {
            scanf("%lld", &a[i]);
            b[i] = a[i];
        }
        sort(a + 1, a + 1 + n);
        for (LL i = 1; i <= n;i++)
        {
            b[i] = lower_bound(a + 1, a + 1 + n, b[i]) - a+1;
        }
        dp[0][0] = 1;
        for (LL i = 1; i <= m;i++)
        {
            memset(c, 0, sizeof(c));
            add(1, dp[i - 1][0]);
            for (LL j = 1; j <= n;j++)
            {
                dp[i][j] = sum(b[j]-1);
                add(b[j], dp[i-1][j]);
            }
        }
        LL ans = 0;
        for (LL i = 1; i <= n;i++)
        {
            ans += dp[m][i];
            ans %= mod;
        }
        printf("Case #%lld: %lld\n",tot++, ans);
    }
    return 0;
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值