先考虑LIS的nlogn解法
我们用dp[len]记录LIS长度为len时的最后一个数的大小,然后不断更新这些值,让每一个值都尽可能小
比如我现在的LIS是1 2 4 6,这时候下一个数是3,那么我们就要更新成1 2 3 6,让前面的数尽可能的小,这样就能让后面的数有更多的机会被加入
因为数字只有10个,我们可以状态压缩,记录每个数字是否出现在使前面的数尽量小的LIS中
然后转移的时候若在状态sta之后添加一个num,则找到第一个大于等于num的数,把它替换成num,若不存在比num大的数则直接添上num
这样既保证了前面的数尽可能的小,也没有改变LIS的长度
上面所说的转移可以预处理出来,next[i][j]表示原来状态为i,加上一个数j之后的状态
dp[dep][sta][K]记录长度为dep,LIS的状态是sta,LIS为K时的答案
注意前导0
1 #include<bits/stdc++.h> 2 using namespace std; 3 typedef long long ll; 4 int Next[1<<10][10],dig[20]; 5 ll dp[20][1<<10][10],cnt[1<<10]; 6 ll get_Next(int sta,int num){ 7 int pos=-1; 8 for(int i=num;i<10;i++) 9 if(sta&(1<<i)){ pos=i; break; } 10 if(pos==-1)sta|=(1<<num); 11 else sta=(sta^(1<<pos))|(1<<num);// 12 return sta; 13 } 14 void PP(){ 15 memset(dp,-1,sizeof(dp)); 16 for(int i=0;i<(1<<10);i++) 17 for(int j=0;j<10;j++){ 18 if(i&(1<<j))cnt[i]++; 19 Next[i][j]=get_Next(i,j); 20 } 21 } 22 ll dfs(int dep,int sta,int K,int flag,int zero){ 23 if(!dep)return cnt[sta]==K?1LL:0LL; 24 if(!flag&&dp[dep][sta][K]!=-1)return dp[dep][sta][K]; 25 int lim=flag?dig[dep]:9; 26 ll ans=0; 27 for(int i=0;i<=lim;i++){ 28 if(zero&&i==0)ans+=dfs(dep-1,0,K,flag&(i==lim),1); 29 else ans+=dfs(dep-1,Next[sta][i],K,flag&(i==lim),0); 30 } 31 if(!flag)dp[dep][sta][K]=ans; 32 return ans; 33 } 34 ll solve(ll x,ll K){ 35 int dd=0; 36 while(x)dig[++dd]=x%10,x/=10; 37 return dfs(dd,0,K,1,1);// 38 } 39 int main(){ 40 PP(); 41 int T; 42 scanf("%d",&T); 43 ll A,B; int K; 44 for(int Case=1;Case<=T;Case++){ 45 scanf("%lld%lld%d",&A,&B,&K); 46 printf("Case #%d: %lld ",Case,solve(B,K)-solve(A-1,K)); 47 } 48 return 0; 49 }