HDU 4352 XHXJ's LIS(数位DP+状压)

时间:2022-12-16 09:36:07
Problem Description #define xhxj (Xin Hang senior sister(学姐)) 
If you do not know xhxj, then carefully reading the entire description is very important.
As the strongest fighting force in UESTC, xhxj grew up in Jintang, a border town of Chengdu.
Like many god cattles, xhxj has a legendary life: 
2010.04, had not yet begun to learn the algorithm, xhxj won the second prize in the university contest. And in this fall, xhxj got one gold medal and one silver medal of regional contest. In the next year's summer, xhxj was invited to Beijing to attend the astar onsite. A few months later, xhxj got two gold medals and was also qualified for world's final. However, xhxj was defeated by zhymaoiing in the competition that determined who would go to the world's final(there is only one team for every university to send to the world's final) .Now, xhxj is much more stronger than ever,and she will go to the dreaming country to compete in TCO final.
As you see, xhxj always keeps a short hair(reasons unknown), so she looks like a boy( I will not tell you she is actually a lovely girl), wearing yellow T-shirt. When she is not talking, her round face feels very lovely, attracting others to touch her face gently。Unlike God Luo's, another UESTC god cattle who has cool and noble charm, xhxj is quite approachable, lively, clever. On the other hand,xhxj is very sensitive to the beautiful properties, "this problem has a very good properties",she always said that after ACing a very hard problem. She often helps in finding solutions, even though she is not good at the problems of that type.
Xhxj loves many games such as,Dota, ocg, mahjong, Starcraft 2, Diablo 3.etc,if you can beat her in any game above, you will get her admire and become a god cattle. She is very concerned with her younger schoolfellows, if she saw someone on a DOTA platform, she would say: "Why do not you go to improve your programming skill". When she receives sincere compliments from others, she would say modestly: "Please don’t flatter at me.(Please don't black)."As she will graduate after no more than one year, xhxj also wants to fall in love. However, the man in her dreams has not yet appeared, so she now prefers girls.
Another hobby of xhxj is yy(speculation) some magical problems to discover the special properties. For example, when she see a number, she would think whether the digits of a number are strictly increasing. If you consider the number as a string and can get a longest strictly increasing subsequence the length of which is equal to k, the power of this number is k.. It is very simple to determine a single number’s power, but is it also easy to solve this problem with the numbers within an interval? xhxj has a little tired,she want a god cattle to help her solve this problem,the problem is: Determine how many numbers have the power value k in [L,R] in O(1)time.
For the first one to solve this problem,xhxj will upgrade 20 favorability rate。
 
Input First a integer T(T<=10000),then T lines follow, every line has three positive integer L,R,K.(
0<L<=R<263-1 and 1<=K<=10).
 
Output For each query, print "Case #t: ans" in a line, in which t is the number of the test case starting from 1 and ans is the answer.  
Sample Input
1
123 321 2
 
Sample Output
Case #1: 139 
 
Author peterae86@UESTC03  
Source 2012 Multi-University Training Contest 6   非常好的一道题,将数位DP 和状压DP以及最长上升子序列结合起来了 题意: 求区间[l,r]内满足将数的每一位取出来当数列看时最长上升子序列为K的数的个数 分析: 很显然的数位DP的调调,但是怎么保存状态呢? 定义dp[i][j][k]:                         i 表示当前进行到的数位 (好像基本上数位DP的第一维都是保存的这个)                         j 是一个压缩的状态,该状态记录的是一个LIS序列,该状态中的 1 的个数即为最长上升子序列的长度                        k 最长上升子序列的长度为 k                        dp[i][j][k] 表示满足 i j k的性质的数的个数 特别解释一下状态压缩的 j : 二进制的每一位1表示该位对应的数字是最长上升子序列的一部分,0则不是 比如  【0101010100】这个状态表示的是最长上升子序列是1,3,5,7的数 这算是一个性质,凡是具有这样性质的数都被这一维记录下来了 比如10357和13057都满足这个状态,都属于这样的数 这样将整个最长上升子序列都被记录了下来,其长度就是这个状态里面1的个数 其他细节代码里都有详细注释,这里特别解释一下状态转移的代码
int turn(int s,int x)//状态转移 对状态s加入数字 x
{
for (int i = x;i < 10;++i)//类似LIS中的更新末位元素使其变小
{
if (s&(1<<i)) //将这个比 x 大的数字 i 用 x这个更小的数去更新
return (s^(1<<i))|(1<<x);//s^(1<<i)是把 i 这一位变成0, |(1<<x)则将 x 这一位变成 1
}
//如果 x 比该 LIS序列的最后一个元素都大则可以加入到后面去
return s|(1<<x);//直接将这一位变成 1
}

看这部分代码的时候仔细回忆一下最长上升子序列的算法(nlongn版)是怎么实现的
/* 
Description: 最长上升子序列

定义d[k]:长度为k的上升序列最末元素(终点元素)
//注意d中元素是单调递增的
初始化:len = 1,d[1]=a[1],
然后对于a[i]:
if a[i] > d[len]
len++,d[len] = a[i]
else
from d[1] to d[len]找到一个j
满足 d[j-1]<a[i]<d[j]
更新d[j] = a[i]
*/

这里的状态转移其实是一样的,
比如数列1 3 5 7 :当扫到8的时候,它就变成了1 3 5 7 8,这就相当于if a[i] > d[len]的情况                               当扫到4的时候,它就变成了1 3 4 7 ,这就相当于 else 的情况 就是将最长上升子序列的算法放到了压缩的状态上处理,所以说这题经典啊! 其他细节代码中见:
#define mem(a,x) memset(a,x,sizeof(a))
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
#include<set>
#include<stack>
#include<cmath>
#include<map>
#include<stdlib.h>
#include<cctype>
#include<string>
using namespace std;
typedef long long ll;
ll dp[22][1<<10][11];
/*
dp[i][j][k]的解释:
i 表示当前进行到的数位 (好像基本上数位DP的第一维都是保存的这个)
j 是一个压缩的状态,该状态记录的是一个LIS序列,该状态中的 1 的个数即为最长上升子序列的长度
k 最长上升子序列的长度为 k
dp[i][j][k]满足 i,j,k 的数的个数
*/
int K;
int turn(int s,int x)//状态转移 对状态s加入数字 x
{
for (int i = x;i < 10;++i)//类似LIS中的更新末位元素使其变小
{
if (s&(1<<i)) //将这个比 x 大的数字 i 用 x这个更小的数去更新
return (s^(1<<i))|(1<<x);//s^(1<<i)是把 i 这一位变成0, |(1<<x)则将 x 这一位变成 1
}
//如果 x 比该 LIS序列的最后一个元素都大则可以加入到后面去
return s|(1<<x);//直接将这一位变成 1
}
int getlen(int s)//返回该状态表示的最长上升子序列的长度
{
int t = 0;
while (s)
{
if (s&1) ++t;
s>>=1;
}
return t;
}
int dig[22];//数的每一个数位
/*
关于 0 的标记:
搜长度为4的时候会搜到 0000
搜长度为3的时候会搜到 000
但实际上0000和000是同一个数
用 zero 记录前一位是不是0
*/
ll dfs(int len,int s,int up,int zero)//当前进行的数位,状态,是否为上界,前一位是否为0
{
if (len == 0) return getlen(s) == K;
if (!up&&dp[len][s][K]!=-1) return dp[len][s][K];
ll ans = 0;int n = 9;if (up) n = dig[len];
for (int i = 0;i <= n;++i)
{
if (i == 0)
{
/*
0 不能为首位 否则会重复计算
当前面有数字的时候就可以加入0
*/
if (zero) ans += dfs(len-1,0,up&&(i==n),zero&&(i==0));
else ans += dfs(len-1,turn(s,i),up&&(i==n),zero&&(i==0));
}
else ans += dfs(len-1,turn(s,i),up&&(i==n),zero&&(i==0));
}
if (!up) dp[len][s][K] = ans;
return ans;
}
ll cal(ll x)
{
int len = 0;
while (x)
{
dig[++len] = x%10;
x/=10;
}
return dfs(len,0,1,1);
}
int main()
{
int T;scanf("%d",&T);
int kas = 0;mem(dp,-1);
while (T--)
{
ll l,r;
scanf("%I64d %I64d %d",&l,&r,&K);
printf("Case #%d: %I64d\n",++kas,cal(r)-cal(l-1));
}
return 0;
}