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

传送门

 

参考博文:

  [1]:http://www.voidcn.com/article/p-ehojgauy-ot.html

 

题解:

  将数字num字符串化;

  求[L,R]区间最长上升子序列长度为 K 的总个数;

题解:

  也不算是题解,只是谈谈我对此题解法的理解;

  学到数位DP的话,应该已经学过状压DP 和 LIS O( nlog(n) )解法吧(默认学过了);

  对于此题,一共就10个不同的数字 "0~9",对于长度为 K 的最长上升子序列,如果不适用记忆化搜索的话,一定会重复计算好多好多次;

  例如,如果 K = 10,那么,最长上升子序列一定顺序包含 0 1 2 3 4 5 6 7 8 9 这十个数字,而最多可以达到19位;

  那么,除这十位外,还有 9 位可以由 0~9 的任意数字随机排列组成,那么重复计算的次数是非常多的。

  关键就是如何记录已经求过的状态?

  首先介绍一下此题如何使用状压;

  正如上例,我们只需关注 0~9 这十个数字是否出现过,以及是否按顺序出现,而不必关心其他位数是由什么组成的,

  那么,这就要用到状压DP的思想了;

  对于 0~9 这十位数:

  9  8  7  6  5  4  3  2  1  0

  _  _  _  _  _  _  _  _  _  _

  每个数字下的横杠可填 0 或 1 这两个数;

  0:不含当前数字;

  1:含有当前数字;

  那么,对于状态 state ,判断其是否含有数字 i ,只需判断 state&(1<<i) 是否为 1 即可;

  首先是预处理:

  定义 lis[ i ] : 状态 i 的最长上升子序列的长度

1 for(int i=0;i < 1<<10;++i)
2 {
3     lis[i]=0;
4     for(int j=0;j < 10;++j)
5         lis[i] += (i&(1<<j)) ? 1:0;
6 }

  接下来,看看这个代码的作用:

1 int LIS(int state,int num)
2 {
3     for(int i=num;i < 10;++i)
4         if(state&(1<<i))
5             return (state^(1<<i))|(1<<num);
6     return state|(1<<num);
7 }

  笼统的解释一下就是:插入 num 后,将 state 中的第一个大于等于 num 位的 1 变为 0,并将 num 位 置为 1;

  例如,假设 state = (1,000,110,001)(2);

  可得,当前 state 已经形成了由 0,4,5,9 构成的长度为 4 的最长上升子序列;

  如果当前加入的数字为 3 ,根据 LIS 的思想,在之前形成的最长上升子序列中找到第一个大于等于 3 的数,

  并将3和其互换,那么返回的值就是 (1,000,101,001)(2);

  那么,这段代码到底是干啥用的呢?

  定义dp[ curPos ][ state ][ K ] : 来到curPos 位置时,状态为 state 所形成的最长上升子序列长度为 K 的总个数;

  核心代码:

 1 ll DFS(int curPos,int state,bool lead,bool limit)
 2 {
 3     if(curPos == -1)
 4         return lis[state] == K ? 1:0;
 5     if(!limit&&dp[curPos][state][K] != -1)
 6         return dp[curPos][state][K];
 7 
 8     int up=limit ? digit[curPos]:9;
 9     ll ans=0;
10     for(int i=0;i <= up;++i)
11         ans += DFS(curPos-1,lead&&i==0 ? 0:LIS(state,i),lead&&i==0,limit&&i==digit[curPos]);
12 
13     if(!limit)
14         dp[curPos][state][K]=ans;
15 
16     return ans;
17 }

  1~9行不再赘述;

  主要看一下第11行的作用;

  第二个参数赋的值为 lead&&i==0 ? 0:LIS(state,i) ,给下一个状态赋 0 很好理解,关键是 LIS(state,i);

  这就对应着上述对 LIS() 的解释;

AC代码:

 1 #include<iostream>
 2 #include<cstdio>
 3 #include<cstring>
 4 using namespace std;
 5 #define ll long long
 6 #define mem(a,b) memset(a,b,sizeof(a))
 7 
 8 ll N,M;
 9 int K;
10 int digit[20];
11 ll dp[20][1<<10][20];
12 int lis[1<<10];
13 
14 int LIS(int state,int num)
15 {
16     for(int i=num;i < 10;++i)
17         if(state&(1<<i))
18             return (state^(1<<i))|(1<<num);
19     return state|(1<<num);
20 }
21 ll DFS(int curPos,int state,bool lead,bool limit)
22 {
23     if(curPos == -1)
24         return lis[state] == K ? 1:0;
25     if(!limit&&dp[curPos][state][K] != -1)
26         return dp[curPos][state][K];
27 
28     int up=limit ? digit[curPos]:9;
29     ll ans=0;
30     for(int i=0;i <= up;++i)
31         ans += DFS(curPos-1,lead&&i==0 ? 0:LIS(state,i),lead&&i==0,limit&&i==digit[curPos]);
32 
33     if(!limit)
34         dp[curPos][state][K]=ans;
35 
36     return ans;
37 }
38 ll Solve(ll x)
39 {
40     int k=0;
41     while(x)
42     {
43         digit[k++]=x%10;
44         x /= 10;
45     }
46     return DFS(k-1,0,true,true);
47 }
48 int main()
49 {
50     int test;
51     scanf("%d",&test);
52     mem(dp,-1);
53 
54 for(int i=0;i < 1<<10;++i)
55 {
56     lis[i]=0;
57     for(int j=0;j < 10;++j)
58         lis[i] += (i&(1<<j)) ? 1:0;
59 }
60     for(int kase=1;kase <= test;++kase)
61     {
62         scanf("%lld%lld%d",&N,&M,&K);
63         printf("Case #%d: %lld\n",kase,Solve(M)-Solve(N-1));
64     }
65     return 0;
66 }
View Code

  

  当然,也可提前预处理出状态 i 插入数字 j 后形成的状态,这就对应着参考博文中的 nex[][] 数组;

posted @ 2019-03-27 18:37  HHHyacinth  阅读(157)  评论(0编辑  收藏  举报