XHXJ's LIS HDU - 4352

HDU - 4352 XHXJ’s LIS (数位dp)

题目链接:https://cn.vjudge.net/contest/163023#problem/B
题目大意:
问L到R,各位数字组成的严格上升子序列的长度为K的个数。
0< L<=R<2^63-1 and 1<=K<=10
注意这里最长上升子序列的定义,和LIS是一样的,不要求是连续的
题目分析:
前置技能:O(nlogn)求最长上升子序列
因为这个最长上升子序列最多十位,所以我们可以用二进制来记录,比如说1346这个子序列用0001011010来表示,最后统计每一位有没有就可以了。

#include 
using namespace std;

typedef long long ll;
ll dp[30][3000][11];
int data[30], k;

int get_num(ll n) //去判断每一位有没有
{
    int ans = 0;
    for(int i = 0; i <= 10; i++)
    {
        if((1 << i) & n) ans++;
    }
    return ans;
}

int get_lis(int now, int x) //更新新的状态
{
    for(int i = x; i <= 9; i++)
    {
        if((1 << i) & now) return ((1 << i) ^ now) | (1 << x);
    }
    return now | (1 << x);
}

ll dfs(int pos, int s, bool zero, bool limit)
{
    if(pos == -1) return get_num(s) == k;
    if(!limit && dp[pos][s][k] != -1) return dp[pos][s][k];
    int up = limit ? data[pos] : 9;
    ll ans = 0;
    for(int i = 0; i <= up; i++)
    {
        ans += dfs(pos - 1, (zero && i == 0) ? 0 : get_lis(s, i), i == 0 && zero, i == data[pos] && limit);
    }
    if(!limit) dp[pos][s][k] = ans;
    return ans;
}

ll solve(ll n)
{
    int pos = 0;
    while(n)
    {
        data[pos++] = n % 10;
        n /= 10;
    }
    return dfs(pos - 1, 0, true, true);
}

int main()
{
    int t, cas = 1;
    memset(dp, -1, sizeof(dp));
    scanf("%d", &t);
    while(t--)
    {
        ll a, b;
        scanf("%lld %lld %d", &a, &b, &k);
        printf("Case #%d: %lld\n", cas++, solve(b) - solve(a - 1));
    }
    return 0;
}

一开始做的时候没有弄懂get_lis这个函数的真正含义,现在懂了。
用的就是求最长上升子序列nlog的算法的思想。
从x开始跑,如果当前位有值,必定是>=x的,是要被替换的,所以用((1 << i) ^ now) | (1 << x);来替换掉。如果>=x位 没有值的话,就更新x位,将它变为1。

你可能感兴趣的:(数位dp)