记忆化数组dp[len][s][K] 表示后len位,s表示当前LIS的状态,题目要求长度为K,的数目
其中s状态:由于LIS最长为10,则用二进制表示当前LIS的中取的数,且不断修改其中某位的值
例如:二进制数 s=00010010(2)表示当前LIS长度为2,且表示为1 4
函数cnt( s, k , id ) 表示 当前LIS状态为s, 且长度为k,现在后面接上一个数 为 id ,则修改状态 需要把id左边的一个1变成零(如果存在1),再在id的位改成1 ;
题目要求[L,R]范围符合条件的数,则把[1,R]的答案减去[1,L]的答案即可
#include<bits/stdc++.h> #define ll long long using namespace std; int K; ll L,R; ll dp[20][(1<<10)+10][20]; int num[20]; ll cnt(ll s,ll &k,int id)//记录当前最长上升子序列状态 状压 { int pos=-1; for(int i=id;i<10;i++) { if(s&(1<<i)) { pos=i; break; } } if(pos==-1) { k++; s|=(1<<id) ; } else { s^=(1<<pos); s|=(1<<id) ; } return s; } ll dfs(int len,ll s,ll k,int limit,int lead)//limit-当前数位是否可随便取 lead-是否有前导零 { if(len==0) { return k==K; } if(!limit&&dp[len][s][K]!=-1) return dp[len][s][K];//从当前到后面所有的数位 都可以无限制任意取 则记忆化 int up =limit? num[len] :9; ll ans=0; for(int i=0;i<=up;i++) { ll tmps=s,tmpk=k; if(i==0&&lead)//处理前导零情况 { tmps=0; } else { tmps=cnt(tmps,tmpk,i); } ans+=dfs(len-1,tmps,tmpk,limit&&i==up,lead&&i==0); } return limit?ans:dp[len][s][K]=ans; } ll solve(ll u) { int len=0; while(u) { num[++len]=u%10;//记录每位的最值 u/=10; } return dfs(len,0,0,1,1); } int main() { memset(dp,-1,sizeof(dp)); //整个题都可以记忆化 int t; scanf("%d",&t); int tt=1; while(t--) { scanf("%lld%lld%d",&L,&R,&K); ll ans=solve(R)-solve(L-1); printf("Case #%d: %lld\n",tt++,ans); } }
“相关推荐”对你有帮助么?
-
非常没帮助
-
没帮助
-
一般
-
有帮助
-
非常有帮助
提交