HDU - 4352 XHXJ's LIS 数位dp+离散化

题目链接:XHXJ's LIS

初看这道题目首先回忆起LIS的O(nlogn)的做法,于是想到定义状态dp[pos][LIS*][k],pos代表当前数位,LIS*代表由前缀构造的LIS数组,k代表最长上升子序列的长度。

问题来了,一个数组肯定不能作为下标,所以我们要把这个数组转化成数字。

注意到数组的每个元素取值范围都是0~9,数组的长度最大为10,所以一个最简单的想法就是把数组转化成一个int类型的数字。

但是很明显需要的内存太大,还有什么办法呢?

因为是严格单调递增的子序列,所以LIS数组中的元素肯定是两两不同的,所有可能的情况总共有


这样一来只需要一个离散化就可以解决问题了,复杂度降低为18 * 1023 * 10。

还有一个小问题,如何离散化?

注意到1023 = 2^10 - 1,所以想到用一个10位的二进制数来表示0~9是否出现。

如果当前LIS数组为LIS[0] = 1, LIS[1] = 3, LIS[2] = 4,则对应的二进制数就为0101100000,这样一来就实现了离散化。

代码如下:

#include <algorithm>
#include <iostream>
#include <cstring>
#include <cstdlib>
#include <vector>
#include <cstdio>
#include <map>

using namespace std;

typedef long long int LL;
LL dp[20][1200][20];
int Lis[20];
int bit[20];
int digit[20];

int Hash(int len)
{
    int ret = 0;
    for (int i = 0; i < len; i++)
        ret += bit[Lis[i]];
    return ret;
}

LL dfs(int pos, int len, int k, int lead, int limit)
{
    if (!pos)
        return len == k;
    if (len > k)
        return 0;
    int code = Hash(len);
    if (!limit && dp[pos][code][k] != -1)
        return dp[pos][code][k];
    int up = limit ? digit[pos] : 9;
    LL ans = 0;
    for (int i = 0; i <= up; i++)
    {
        int idx = lower_bound(Lis, Lis + len, i) - Lis;
        int temp = Lis[idx];
        Lis[idx] = i;
        ans += dfs(pos-1, (lead&&!i)?0:len+(idx==len), k, lead&&!i, limit&&i==up);
        Lis[idx] = temp;
    }
    return limit ? ans : dp[pos][code][k] = ans;
}

LL cal(LL n, int k)
{
    int len = 0;
    while (n)
    {
        digit[++len] = n % 10;
        n /= 10;
    }
    return dfs(len, 0, k, 1, 1);
}

int main()
{
    //freopen("test.txt", "r", stdin);
    bit[0] = 1;
    for (int i = 1; i <= 9; i++)
        bit[i] = bit[i - 1] * 2;
    memset(dp, -1, sizeof(dp));
    int T, Case = 1;
    scanf("%d", &T);
    while (T--)
    {
        LL L, R;
        int K;
        scanf("%I64d%I64d%d", &L, &R, &K);
        printf("Case #%d: ", Case++);
        printf("%I64d\n", cal(R, K) - cal(L - 1, K));
    }
    return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值