HDU 4352 XHXJ's LIS 数位状压DP

题目大意:

就是现在对于每个正整数可以将其每位视为一个数形成一个串, 那么这一组数就存在一个最长上升子序列, 对于每组给出的L, R (0 < L <= R <= 2^63 - 1) 求出在区间[L, R]中有多少个数在视作这样的一组数后最长上升子序列的长度是K


大致思路:

看数据范围很明显是数位DP, 考虑最长上升子序列这个子问题中常见的O(nlogn)的解法:

用dp[i]表示直到当前位, 在所有长度为i的上升子序列中结尾最小的是dp[i], 那么dp[i]满足单调性, 二分求解

那么在这个题中, 由于LIS的长度不可能超过10, 可以用状态压缩来存储当前LIS的状态, 如果状压的数的二进制中1的个数表示最大长度

而状态压缩中第i个1的位置p, 表示以所有上升子序列中长度为i的子序列结尾最小是p, 于是就可以进行状态转移了, 转移可以预处理出来

然后数位DP的时候, 记忆化即可


代码如下:

Result  :  Accepted     Memory  :  3388 KB     Time  :  124 ms

/*
 * Author: Gatevin
 * Created Time:  2015/8/3 15:59:02
 * File Name: Sakura_Chiyo.cpp
 */
#include<iostream>
#include<sstream>
#include<fstream>
#include<vector>
#include<list>
#include<deque>
#include<queue>
#include<stack>
#include<map>
#include<set>
#include<bitset>
#include<algorithm>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cctype>
#include<cmath>
#include<ctime>
#include<iomanip>
using namespace std;
const double eps(1e-8);
typedef long long lint;


/*
 * 用dp[bit][num][LIS]表示当前填到了第bit位, 填了num, 最长上升子序列的状况为LIS的情况有多少种
 * 先预处理出LIS的转移trans[LIS][0~9] = ?
 * 然后在数位DP的时候记忆化搜索即可
 * 
 * 注意这里LIS状态压缩的含义:
 * 这个数的二进制第i个1的位置表示在当前的序列中, 长度为i的上升子序列结尾的数最小是多少
 * 例如0110110010就表示当前LIS以8结尾, 当前长度为1, 2, 3, 4, 5的上升子序列最小的结尾分别是1, 2, 3, 4, 8
 * 而这个二进制中的1的总个数就是LIS的长度
 * 于是可以预处理LIS的转移
 */

lint dp[20][1 << 10][11];//这里我的dp只是用来记忆化搜索而已.....
int trans[1 << 10][10];
int ones[1 << 10];//1的个数
lint L, R;
int K;

void init()
{
    memset(dp, -1, sizeof(dp));
    for(int i = 0; i < (1 << 10); i++)//当前LIS状态i
        for(int j = 0; j < 10; j++)//来了一个数j
        {
            trans[i][j] = i;
            for(int k = j; k < 10; k++)
                if(i & (1 << (9 - k)))
                {
                    trans[i][j] ^= (1 << (9 - k));
                    break;
                }
            trans[i][j] |= (1 << (9 - j));
        }
    
    for(int i = 0; i < (1 << 10); i++)
        ones[i] = __builtin_popcount(i);
    return;
}

int bit[20];//记录边界的各位的值, bit[i]表示10^i的系数

lint dfs(int pos, int s, bool lim, bool zero)//当前填pos位, LIS状态为s, 是否有上界限制, 有无前导零
{
    if(pos == -1 && zero) return K == 1;//这个数只有一个0, LIS是0
    if(pos == -1 && !zero) return ones[s] == K;
    if(!lim && !zero && dp[pos][s][K] != -1) return dp[pos][s][K];
    lint ret = 0;
    if(zero)
    {
        ret += dfs(pos - 1, s, 0, 1);//继续前导零
        for(int i = 1; i < bit[pos]; i++)
            ret += dfs(pos - 1, trans[s][i], 0, 0);
        if(lim)
            ret += dfs(pos - 1, trans[s][bit[pos]], 1, 0);
        else
            for(int i = max(1, bit[pos]); i < 10; i++)
                ret += dfs(pos - 1, trans[s][i], 0, 0);
    }
    else
    {
        for(int i = 0; i < bit[pos]; i++)
            ret += dfs(pos - 1, trans[s][i], 0, 0);
        if(lim)
            ret += dfs(pos - 1, trans[s][bit[pos]], 1, 0);
        else
            for(int i = bit[pos]; i < 10; i++)
                ret += dfs(pos - 1, trans[s][i], 0, 0);
    }
    if(!lim && !zero) dp[pos][s][K] = ret;
    return ret;
}

lint calc(lint num)//区间[0, num]之间满足条件的个数
{
    int len = 0;
    while(num)
    {
        bit[len++] = num % 10;
        num /= 10;
    }
    //一共有len位
    return dfs(len - 1, 0, 1, 1);
}

int main()
{
    init();
    int T;
    scanf("%d", &T);
    for(int cas = 1; cas <= T; cas++)
    {
        scanf("%I64d %I64d %d", &L, &R, &K);
        printf("Case #%d: %I64d\n", cas, calc(R) - calc(L - 1));
    }
    return 0;
}


评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值