XHXJ's LIS
刚发现我写题解从来都没有把题目名字写出来过,从今天开始起补上
又是一个新的数位DP姿势,学完各位大神的博客来自己总结一发
题目链接:HDOJ4352
题意:给定【L,R】,求各个数的数位上数字的LIS为K的数有多少个,看到L,R的数值大小就知道是数位DP,看到K最大是10,也可以猜到K是dp中状态的一维,但是怎么放进去呢?
先想想模板,dp【pos】【status】肯定还是需要的,pos是数位,那么status怎么定义呢?
此题状态定义显然与LIS有关,K最多为10,想到状态压缩,每一个数字0-9是否出现,在status用0或者1表示,所以一个整数可以表示出0-9是否出现过
LIS有种O(nlogn)的做法:
dp【i】:记录LIS长度为i的时候,最后一个数的大小,越小越好的思路
借鉴到这道题来的时候,需要让出现的数字尽可能的小,所以status中,低位出现的1就要尽量多(这就可以写个函数来实现)
既然是LIS长度为K,那么K肯定需要是dp中的一维状态变量
所以状态量定义为dp【pos】【status】【K】,其中status中1的个数就是LIS的长度
然后呢,在LIS中有个特殊情况必须处理好。如果某个数位中已经出现了0,那么之前的LIS就可以断下来了(因为再也不可能更长)
所以在记忆化的时候,需要一个变量来记录是不是当前有0,或者说前导0
剩下的贴代码可以直接懂了,跟模板没有太多区别了
#include<map> #include<set> #include<math.h> #include<time.h> #include<iostream> #include<cstdio> #include<queue> #include<stack> #include<stdio.h> #include<cstring> #include<string.h> #include<algorithm> #include<cstdlib> using namespace std; #define lson rt<<1,l,mid #define rson rt<<1|1,mid+1,r #define ll rt<<1 #define rr rt<<1|1 #define LL long long #define ULL unsigned long long #define maxn 1050 #define maxnum 1000050 #define eps 1e-6 #define input freopen("input.txt","r",stdin) #define output freopen("output.txt","w",stdout) /* 函数解释: getnewstatus: LIS的O(nlogn) getonenum: 计算s中有多少个1 */ int digit[50],K; __int64 dp[50][1<<12][12]; __int64 L,R; int getnewstatus(int x,int s){ for(int i=x;i<10;i++) if (s&(1<<i)) return (s^(1<<i))|(1<<x); //如果在某个高位有可以替代的值 //用x这位去替代i这一位 //数值会是更小的 return s|(1<<x); //找不到的话,就把第x这位添加进去 } int getonenum(int s){ //实参代入时为status int num=0; while(s){ if (s%2) num++; s>>=1; } return num; } __int64 dfs(int pos,int status,bool flag,bool zero){ if (pos==0) return getonenum(status)==K; if (flag&&dp[pos][status][K]!=-1) return dp[pos][status][K]; int num=flag?9:digit[pos]; __int64 ans=0; for(int i=0;i<=num;i++) //注意到0这个特殊数值 ans+=dfs(pos-1,zero&&(i==0)?0:getnewstatus(i,status),flag||i<num,zero&&(i==0)); if (flag) dp[pos][status][K]=ans; return ans; } __int64 calc(__int64 n){ int len=0; while(n){ digit[++len]=n%10; n/=10; } return dfs(len,0,0,1); } int main(){ //input; memset(dp,-1,sizeof(dp)); int T; scanf("%d",&T); for(int Case=1;Case<=T;Case++){ memset(digit,0,sizeof(digit)); scanf("%I64d%I64d%d",&L,&R,&K); printf("Case #%d: %I64d\n",Case,calc(R)-calc(L-1)); } return 0; }