HDU

来源:互联网 发布:淘宝店铺淘金币签到 编辑:程序博客网 时间:2024/06/02 05:52

题目链接: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;}