hdu4352(数位DP + LIS(nlogn))

题目描述: 

给定一个区间中,将区间的每一个数看成一个字符串,求这个区间内每个字符串的最大上升

子序列等于k的个数。

可以采用nlognLIS(用一个C数组记录长度为i的最大上升子序列的结尾最小值)

所以可以采用dfs暴力枚举每一个数,并且由于数的长度最大为18位,

所以c数组可以用一个状态数表示。

dp[len][state][k],代表长度为len的数,c数组状态为state,上升子序列长度等于k的个数。

为什么要加k这一维?因为如果有多组询问,k不相同,那么就不能用之前计算过的dp[len][state]状态,

它保存的其实是,上升子序列长度等于之前k的个数。

可以记忆化的理由:分析到如果不同数的前缀对C数组产生的一样,那么两者等价,那么可以记忆化。

个人理解:其实数位DP考虑记忆化,就要从不同前缀对之后len位的影响考虑。

#include <stdio.h>
#include <string.h>
#include <algorithm>
#include <iostream>
using namespace std;
#define LL  long long
LL dp[30][1<<10][15];  //长度为30,最大上升子序列状态为s,是否有等于k的个数
int digit[100];
int K;
int bit(int state)
{
    int cnt=0;
    while(state>0)
    {
        if(state & 1 ==1)
            cnt++;
        state>>=1;
    }
    return cnt;
}

int solve(int state,int i)
{
    int j;
    int ok=0;
    for(j=i;j<=9;j++)
    {
        if(state & (1<<j))
        {
            ok=1;  break;
        }
    }
    int s;
    if(ok==1)
      s=( state ^ (1<<j) )| (1<<i);
    else
      s=state | (1<< i);
    return s;
}

LL dfs(int len,int state,bool z,bool fp)
{
    if(  len==0  )
        return  bit(state)==K;
    if(!fp && dp[len][state][K] != -1)
         return dp[len][state][K];
    LL ret = 0 ;
    int  fpmax = fp ? digit[len] : 9;
    for(int i=0;i<=fpmax;i++)
    {
        int s=solve(state,i);
        ret += dfs(len-1,(z&&(i==0)) ? 0 : s, z&&(i==0) ,fp && i == fpmax);
    }
    if(!fp)
        dp[len][state][K] = ret;
    return ret;
}

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

int main()
{
   //freopen("test.txt","r",stdin);
    LL a,b;
    int t,Case=0;
     scanf("%d",&t);
     memset(dp,-1,sizeof(dp));
     while(t--)
     {
          scanf("%lld%lld%d",&a,&b,&K);
          if(a==b)
              printf("Case #%d: %d
",++Case,0);
          printf("Case #%d: %lld
",++Case,f(b)-f(a-1));
     }

    return 0;
}
原文地址:https://www.cnblogs.com/xianbin7/p/4833466.html