数位DP,以前接触过。没有深入了解。这次想系统的了解学习一下DP,特意重新看了下。首先,放上模板
typedef long long ll;
2.int a[20];
3.ll dp[20][state];//不同题目状态不同
4.ll dfs(int pos,/*state变量*/,bool lead/*前导零*/,bool limit/*数位上界变量*/)//不是每个题都要判断前导零
5.{
6. //递归边界,既然是按位枚举,最低位是0,那么pos==-1说明这个数我枚举完了
7. if(pos==-1) return 1;/*这里一般返回1,表示你枚举的这个数是合法的,那么这里就需要你在枚举时必须每一位都要满足题目条件,也就是说当前枚举到pos位,一定要保证前面已经枚举的数位是合法的。不过具体题目不同或者写法不同的话不一定要返回1 */
8. //第二个就是记忆化(在此前可能不同题目还能有一些剪枝)
9. if(!limit && !lead && dp[pos][state]!=-1) return dp[pos][state];
10. /*常规写法都是在没有限制的条件记忆化,这里与下面记录状态是对应,具体为什么是有条件的记忆化后面会讲*/
11. int up=limit?a[pos]:9;//根据limit判断枚举的上界up;这个的例子前面用213讲过了
12. ll ans=0;
13. //开始计数
14. for(int i=0;i<=up;i++)//枚举,然后把不同情况的个数加到ans就可以了
15. {
16. if() ...
17. else if()...
18. ans+=dfs(pos-1,/*状态转移*/,lead && i==0,limit && i==a[pos]) //最后两个变量传参都是这样写的
19. /*这里还算比较灵活,不过做几个题就觉得这里也是套路了
20. 大概就是说,我当前数位枚举的数是i,然后根据题目的约束条件分类讨论
21. 去计算不同情况下的个数,还有要根据state变量来保证i的合法性,比如题目
22. 要求数位上不能有62连续出现,那么就是state就是要保存前一位pre,然后分类,
23. 前一位如果是6那么这意味就不能是2,这里一定要保存枚举的这个数是合法*/
24. }
25. //计算完,记录状态
26. if(!limit && !lead) dp[pos][state]=ans;
27. /*这里对应上面的记忆化,在一定条件下时记录,保证一致性,当然如果约束条件不需要考虑lead,这里就是lead就完全不用考虑了*/
28. return ans;
29.}
30.ll solve(ll x)
31.{
32. int pos=0;
33. while(x)//把数位都分解出来
34. {
35. a[pos++]=x%10;//个人老是喜欢编号为[0,pos),看不惯的就按自己习惯来,反正注意数位边界就行
36. x/=10;
37. }
38. return dfs(pos-1/*从最高位开始枚举*/,/*一系列状态 */,true,true);//刚开始最高位都是有限制并且有前导零的,显然比最高位还要高的一位视为0嘛
39.}
40.int main()
41.{
42. ll le,ri;
43. while(~scanf("%lld%lld",&le,&ri))
44. {
45. //初始化dp数组为-1,这里还有更加优美的优化,后面讲
46. printf("%lld\n",solve(ri)-solve(le-1));
47. }
48.}
这可以看做,以前在cf上的一道题。也就是记忆化搜索。利用dp进行存储,而dfs进行搜索回溯达到记忆化搜索的目的。
例子:HDOJ 2089
题目:http://acm.hdu.edu.cn/showproblem.php?pid=2089
这是一道入门题。DP的条件也很简单就是判断当前位和上一位不是6 2并且当前位不为4。
#include<bits/stdc++.h>
#define INF 1e18
#define inf 1e9
#define min(a,b) a<b?a:b
#define max(a,b) a>b?a:b
#define lson l,m,rt<<1
#define rson m+1,r,rt<<1|1
#define IOS ios_base::sync_with_stdio(false),cin.tie(0),cout.tie(0)
using namespace std ;
typedef long long ll;
typedef unsigned long long ull;
const ll mod = 1e9+7;
int num[20];
int dp[20][2];
ll dfs(int cur,int first,int sta,bool limit){
if(cur == -1) return 1;
if(!limit && dp[cur][sta] != -1) return dp[cur][sta];
int up = limit?num[cur]:9;
int cnt = 0;
for(int i = 0 ; i < up+1 ; i++){
if(first == 6 && i == 2) continue;
if(i == 4) continue;
cnt += dfs(cur-1,i,i==6,limit && i==num[cur]);
}
if(!limit) dp[cur][sta] = cnt;
return cnt;
}
ll solve(ll n){
int len = 0;
while(n){
num[len++] = n%10;
n /= 10;
}
return dfs(len-1,-1,0,true);
}
int main(){
int n,m;
while(cin>>n>>m){
if(!n && !m) break;
memset(dp,-1,sizeof(dp));
cout<<(solve(m) - solve(n-1))<<endl;
}
return 0;
}
HDOJ 4737
题目:http://acm.hdu.edu.cn/showproblem.php?pid=4734
这道题目也是数位DP,真是虐我千百遍。一些小细节,让我一直A不掉。首先分析题目。先写出了f函数。然后很容易发现1e9-1的时候f函数的值最大,为4599。开DP数组。DP[len][sum]。这里sum就可以当做状态。不过这里我们的sum表示应该是余数,就是还能有多少背包是有用的。
然后进行记忆化搜索。注意,memset放在循环外面。因为放里面,每次都要重头来过!
#include<bits/stdc++.h>
#define INF 1e18
#define inf 1e9
#define min(a,b) a<b?a:b
#define max(a,b) a>b?a:b
#define lson l,m,rt<<1
#define rson m+1,r,rt<<1|1
#define IOS ios_base::sync_with_stdio(false),cin.tie(0),cout.tie(0)
using namespace std ;
typedef long long ll;
typedef unsigned long long ull;
const ll mod = 1e9+7;
const ll _max = 5000;
int num[20];
int dp[20][_max];
int f(int x){
if(x==0) return 0;
int ans = f(x/10);
return ans*2+(x%10);
}
int wei;
int dfs(int cur,int sum,bool limit){
if(cur == -1) return sum <= wei;
if(sum > wei) return 0;
if(!limit &&dp[cur][wei-sum] != -1) return dp[cur][wei-sum];
int up = limit?num[cur]:9;
int ans =0;
for(int i = 0 ; i < up+1 ; i++){
ans += dfs(cur-1,sum+i*(1<<cur),limit && i==num[cur]);
}
if(!limit) dp[cur][wei-sum] = ans;
return ans;
}
int solve(ll x){
int len = 0;
while(x){
num[len++] = x%10;
x /= 10;
}
return dfs(len-1,0,1);
}
int main(){
int T;
scanf("%d",&T);
memset(dp,-1,sizeof(dp));
for(int Cas = 1 ; Cas <= T ; Cas++){
int a,b;
scanf("%d%d",&a,&b);
wei = f(a);
printf("Case #%d: %d\n",Cas,solve(b));
}
return 0;
}
POJ 3252
题目:http://poj.org/problem?id=3252
这题目的意思就是求a到b之间二进制中0大于1的数。
这里面需要注意的技巧有两个。
1.因为在过程中,0的数小于1不算非法。但是需要让数组有效所以运动哈希知识,调整零刻度(我这里就是加40)。
2.因为在dfs的过程中,会有含有前导零的数。但是前导零我们并不能算。所以我们需要加入bool lead来控制。所以在这题里面。进行记忆化的条件是!lead && !limit。为什么需要lead,是和limit的作用相同。
#include<bits/stdc++.h>
#define INF 1e18
#define inf 1e9
#define min(a,b) a<b?a:b
#define max(a,b) a>b?a:b
#define lson l,m,rt<<1
#define rson m+1,r,rt<<1|1
#define IOS ios_base::sync_with_stdio(false),cin.tie(0),cout.tie(0)
using namespace std ;
typedef long long ll;
typedef unsigned long long ull;
const ll mod = 1e9+7;
const ll _max = 5000;
int num[80];
int dp[80][80];
int dfs(int cur,int sum,bool lead,bool limit){
if(cur == -1) return sum >= 40;
if(!limit && !lead && dp[cur][sum] != -1) return dp[cur][sum];
int up = limit?num[cur]:1;
int ans =0;
for(int i = 0 ; i < up+1 ; i++){
if(lead && i==0 ) //前导零
ans += dfs(cur-1,sum,lead,limit && i==num[cur]);
else
ans += dfs(cur-1,sum+(i?-1:1),lead && i==0,limit && i==num[cur]);
}
if(!limit && !lead) dp[cur][sum] = ans;
return ans;
}
int solve(ll x){
int len = 0;
while(x>0){
num[len++] = x&1;
x>>=1;
}
return dfs(len-1,40,1,1);
}
int main(){
memset(dp,-1,sizeof(dp));
ll a,b;
while(cin>>a>>b){
cout<<solve(b)-solve(a-1)<<endl;
}
return 0;
}