题目大意:
定义f(i)表示将i看成字符串的最长上升子序列长度。
给定l,r,k,求满足l<=i<=r且f(i)=k的个数。
1<=l<=r<=10^18,1<=k<=10,T<=10000。
解题思路:
注意是最长上升序列,如果是不下降序列就无法做了。
回忆O(nlogn)求 LIS 的过程,维护一个上升序列,每次新加一个数的时候,用它去替换里面最小的大于它的数,最后序列长度就是答案。
对于本题,因为数位只可能是 0 到 9,所以可以考虑用一个10位二进制数来唯一确定这个需要维护的序列。
设f(i,j,lim,z)为从高到低填了前 i 位,之前部分的序列情况为j,是否封顶,前缀是否全为0的数字个数,然后dp即可。
注意询问组数很多,所以要保证dp记忆化具有通用性,不能每次重新dp。
时间复杂度O(logr*2^10*10)。
#include<bits/stdc++.h>
#define ll long long
using namespace std;
ll getint()
{
ll i=0,f=1;char c;
for(c=getchar();(c!='-')&&(c<'0'||c>'9');c=getchar());
if(c=='-')c=getchar(),f=-1;
for(;c>='0'&&c<='9';c=getchar())i=(i<<3)+(i<<1)+c-'0';
return i*f;
}
int T,k,num[25];
ll l,r,dp[25][1<<10][11];
int get_num(int s)
{
int res=0;
for(int i=0;i<10;i++)res+=(s>>i&1);
return res;
}
int get_s(int s,int x)
{
for(int i=x;i<10;i++)
if(s>>i&1)return (s^(1<<i))|(1<<x);
return s|(1<<x);
}
ll dfs(int pos,int s,bool lim,bool z)
{
if(pos==-1)return get_num(s)==k;
if(!lim&&dp[pos][s][k]!=-1)return dp[pos][s][k];
int end=lim?num[pos]:9;ll res=0;
for(int i=0;i<=end;i++)
res+=dfs(pos-1,(i==0&&z)?0:get_s(s,i),lim&&(i==end),z&&(i==0));
if(!lim) dp[pos][s][k]=res;
return res;
}
ll solve(ll n)
{
int len=0;
while(n)num[len++]=n%10,n/=10;
return dfs(len-1,0,1,1);
}
int main()
{
//freopen("lx.in","r",stdin);
memset(dp,-1,sizeof(dp));
T=getint();
for(int t=1;t<=T;t++)
{
l=getint(),r=getint(),k=getint();
printf("Case #%d: %lld\n",t,solve(r)-solve(l-1));
}
return 0;
}