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