数位DP + LIS
题意:
每一个数字都有最长上升子序列,给出一个范围,求出里面的最长上升子序列为k 的数字个数。
思路:
数位DP枚举的是每一个数位上的所有情况,当枚举到一个数字的时候其最长上升子序列可以压缩在一个二进制中,而二进制1出现的次数就是最长上升子序列的长度,其思想和LIS 的思想抑制。
定义: dp[i][s][k] 表示从最高位到第i位数字组成的数字最长上升子序列的长度为k的个数。
而在 dfs(pos,s,z,e) 的时候pos表示数字的第几位,s表示组成的序列状态(不断更新),z表示上一位是否为0(关乎到s的更新,如果之前都是0的话s = 0),e表示是否为边界(判断是否可以记忆化搜索)。
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
typedef long long LL;
const int maxn = 20;
LL n,m,k;
int bit[maxn];
LL dp[maxn][1<<10][maxn];
int Getnum(int x)
{
int ans = 0;
while(x) {
if(x&1) ans++;
x >>= 1;
}
return ans;
}
int GetNew(int s,int x)
{
for(int i = x;i < 10; i++)
if(s&(1<<i)) return (s^(1<<i) | (1<<x));
return s|(1<<x);
}
LL dfs(int pos,int s,int z,int e)
{
if(pos < 0) return Getnum(s) == k;
if(!e && dp[pos][s][k] != -1) return dp[pos][s][k];
int End = e ? bit[pos] : 9;
LL ans = 0;
for(int i = 0;i <= End; i++) {
ans += dfs(pos-1,(z&&i==0)?0:GetNew(s,i),z&&i==0,e&&i==End);
}
if(!e) dp[pos][s][k] = ans;
return ans;
}
LL solve(LL x)
{
int pos = 0;
while(x) {
bit[pos++] = x%10;
x /= 10;
}
return dfs(pos-1,0,1,1);
}
int main(int argc, char const *argv[])
{
// freopen("in.txt","r",stdin);
memset(dp,-1,sizeof(dp));
int ncase = 1,tt;
scanf("%d",&tt);
while(tt--) {
scanf("%I64d%I64d%I64d",&n,&m,&k);
printf("Case #%d: %I64d\n",ncase++,solve(m) - solve(n-1));
}
return 0;
}