题目链接:
http://acm.hdu.edu.cn/showproblem.php?pid=4352
题目意思:
求区间L到R之间的数A满足A的的数位的最长递增序列的长度为K的数的个数。
解题思路:
数位dp+LIS+状态压缩
根据求LIS的nlogn的思想,由于k最多为10,也就是最长递增序列的长度最多为10,可以状态压缩10位,维护前面的已选的递增序列,并及时更新。
dp[i][j][k]表示后面还有i位,前面状态为k,总共需要最长递增序列为j时的总数。
代码:
更新时用两种方式实现都可以。
#include<iostream> #include<cmath> #include<cstdio> #include<cstdlib> #include<string> #include<cstring> #include<algorithm> #include<vector> #include<map> #include<set> #include<stack> #include<list> #include<queue> #include<ctime> #define eps 1e-6 #define INF 0x3fffffff #define PI acos(-1.0) #define ll __int64 #define lson l,m,(rt<<1) #define rson m+1,r,(rt<<1)|1 #pragma comment(linker, "/STACK:1024000000,1024000000") using namespace std; ll dp[25][12][1200]; int bit[25],cnt,k; ll dfs(int cur,int s,int flag,int num,int * a) { if(!cur) { if(num==k) { dp[cur][num][s]=1; return 1; } return 0; } if(!flag&&dp[cur][k][s]!=-1) return dp[cur][k][s]; ll res=0; int Max=flag?bit[cur]:9; int tmp[25]; for(int i=0;i<=Max;i++) { for(int j=0;j<=num;j++) //如果用二分来写的话,要防止后面最前面数组的改动 tmp[j]=a[j]; if(!s&&!i) //注意前置零,不能记为一个数字 { res+=dfs(cur-1,0,0,0,tmp); continue; } //但当首位不为0时,0又是一个有效的数字 if(i>tmp[num]) //找到一个 { tmp[num+1]=i; res+=dfs(cur-1,s|(1<<i),flag&&(i==Max),num+1,tmp); } else { //找到第一个大于等于的 int t=lower_bound(tmp,tmp+num+1,i)-tmp; int tt=tmp[t]; tmp[t]=i; res+=dfs(cur-1,s-(1<<tt)+(1<<i),flag&&(i==Max),num,tmp); } //第二种实现方式,比较直观,而且复杂度稍微低点 /* if((1<<i)>s) res+=dfs(cur-1,s|(1<<i),flag&&(i==Max),num+1); else if((1<<i)&s) res+=dfs(cur-1,s,flag&&(i==Max),num); else { for(int j=i+1;j<=9;j++) { if((1<<j)&s) { res+=dfs(cur-1,(s-(1<<j))|(1<<i),flag&&(i==Max),num); break; } } }*/ } if(!flag) dp[cur][k][s]=res; return res; } ll init(ll a) { cnt=0; while(a) { bit[++cnt]=a%10; a/=10; } int aa[25]; aa[0]=-1; return dfs(cnt,0,1,0,aa); } int main() { memset(dp,-1,sizeof(dp)); int t; ll a,b; scanf("%d",&t); for(int ca=1;ca<=t;ca++) { scanf("%I64d%I64d%d",&a,&b,&k); //printf("%I64d %I64d\n",init(a,k),init(b,k)); printf("Case #%d: %I64d\n",ca,init(b)-init(a-1)); } return 0; }