XHXJ's LIS
http://acm.hdu.edu.cn/showproblem.php?pid=4352
题意:
询问L~R之间多少个数满足以下条件:将数字的每一位上的数字写成一个序列(这个序列每个数不超过10,长度不超过20),然后这个序列的最长上升子序列的长度为k。
分析:
数位dp。
这个状态有点特殊。首先考虑nlogn求最长上升子序列的过程,记录每长度为k上升子序列中结尾的最小的是谁。那么可以记录这个数组,每次转移更新数组即可。但是直接记录是空间开不下。但是发现这个题有一个性质:最大长度为10,最大的数字为10,而且是上升的。如果记入一个长度为10的01串。第b位上的,是从头开始第a个1,表示长度为a的最长上升子序列的结尾最小的数是b。由于是上升的,每个位上只有一个1,而且长度为10,刚好开得下。
加入一个数后,找到第一个比它大的数,然后去掉这个,加上新加的。
代码:
1 #include<cstdio> 2 #include<algorithm> 3 #include<cstring> 4 #include<cmath> 5 #include<iostream> 6 #include<cctype> 7 #include<set> 8 #include<vector> 9 #include<queue> 10 #include<map> 11 #define fi(s) freopen(s,"r",stdin); 12 #define fo(s) freopen(s,"w",stdout); 13 using namespace std; 14 typedef long long LL; 15 16 inline LL read() { 17 LL x=0,f=1;char ch=getchar();for(;!isdigit(ch);ch=getchar())if(ch=='-')f=-1; 18 for(;isdigit(ch);ch=getchar())x=x*10+ch-'0';return x*f; 19 } 20 21 int a[22], tot, k; 22 LL dp[22][(1 << 10) + 5][11]; 23 24 int getnxt(int i,int s) { 25 for (int j=i; j<=9; ++j) 26 if ((1 << j) & s) { s ^= (1 << j); break; } 27 return s | (1 << i); // !!! 28 } 29 int getbit(int x) { 30 int res = 0; 31 while (x) res += (x & 1), x >>= 1; 32 return res; 33 } 34 LL dfs(int x,int sta,bool lim,bool fir) { 35 if (!x) return getbit(sta) == k; 36 if (!lim && dp[x][sta][k] != -1) return dp[x][sta][k]; 37 LL res = 0; 38 int u = lim ? a[x] : 9; 39 for (int i=0; i<=u; ++i) 40 res += dfs(x - 1, fir&&i==0 ? 0 : getnxt(i, sta), lim&&i==a[x], fir&&i==0); 41 if (!lim) dp[x][sta][k] = res; 42 return res; 43 } 44 LL Calc(LL x) { 45 tot = 0; 46 while (x) { 47 a[++tot] = x % 10; 48 x /= 10; 49 } 50 return dfs(tot, 0, 1, 1); 51 } 52 int main() { 53 memset(dp, -1, sizeof(dp)); 54 int T = read(); 55 for (int t=1; t<=T; ++t) { 56 LL L = read(), R = read(); k = read(); 57 printf("Case #%d: ",t); 58 printf("%lld ", Calc(R) - Calc(L - 1)); 59 } 60 return 0; 61 }