数位DP

https://wenku.baidu.com/view/d2414ffe04a1b0717fd5dda8.html

Jason的特殊爱好

 FZU - 2113 

题意:统计a到b之间有多少个数位1.

数位入门~

 1 #include <iostream>
 2 #include <cstdio>
 3 using namespace std;
 4 #define ll long long
 5 ll base[20];
 6 int dig[25];
 7 void init(){
 8     base[0]=1;
 9     for(int i=1;i<19;i++)
10         base[i]=base[i-1]*10;
11 }
12 ll f(ll x){
13     ll res=0,m=x;
14     int pos=0;
15     while(x){
16         dig[++pos]=x%10;
17         x/=10;
18     }
19     for(int i=1;i<=pos;i++){
20         ll r=m%base[i-1];
21         ll l=m/base[i];
22         if(dig[i]>1){
23             res+=(l+1)*base[i-1];
24         }else if(dig[i]==1){
25             res+=l*base[i-1]+r+1;
26         }else{
27             res+=l*base[i-1];
28         }
29     }
30     return res;
31 }
32 int main(){
33     ll a,b;
34     init();
35     while(scanf("%I64d%I64d",&a,&b)!=EOF){
36         printf("%I64d
",f(b)-f(a-1));
37     }
38 }
View Code

Bomb

 HDU - 3555 

不要62

 HDU - 2089

题意:求a到b中不含数位4和62的数字个数。

 1 #include <bits/stdc++.h>
 2 using namespace std;
 3 #define ll long long
 4 int dig[20];
 5 int dp[20][2];
 6 
 7 int dfs(int pos,int pre,int sta,int lim){
 8     if(pos==-1) return 1;
 9     if(!lim&&dp[pos][sta]!=-1) return dp[pos][sta];
10     int up=lim?dig[pos]:9;
11     int temp=0;
12     for(int i=0;i<=up;i++){
13         if(pre==6&&i==2) continue;
14         if(i==4) continue;
15         temp+=dfs(pos-1,i,i==6,lim&&i==dig[pos]);
16     }
17     if(!lim) dp[pos][sta]=temp;
18     return temp;
19 }
20 int solve(int x){
21     int pos=0;
22     while(x){
23         dig[pos++]=x%10;
24         x/=10;
25     }
26     return dfs(pos-1,-1,0,true);
27 }
28 
29 int main(){
30     int a,b;
31     memset(dp,-1,sizeof(dp));
32     while(scanf("%d%d",&a,&b)&&(a||b)){
33         printf("%d
",solve(b)-solve(a-1));
34     }
35 }
View Code

F(x)

 HDU - 4734 

题意:定义了f(x)函数,让求在区间[0,b]中的f值小于等于f(a)的有多少个数。

 1 #include <bits/stdc++.h>
 2 using namespace std;
 3 int f;
 4 int dig[20];
 5 int dp[20][5010];  //dp[pos][sum]还需枚举pos位,并且剩下的pos位和为sum
 6 int cal(int x){
 7     if(x==0) return 0;
 8     int ans=cal(x/10);
 9     return ans*2+(x%10);
10 }
11 int dfs(int pos,int sum,int lim){
12     if(pos==-1) return sum<=f;
13     if(sum>f) return 0;
14     if(!lim&&dp[pos][f-sum]!=-1) return dp[pos][f-sum];
15     int up=lim?dig[pos]:9;
16     int ans=0;
17     for(int i=0;i<=up;i++){
18         ans+=dfs(pos-1,sum+i*(1<<pos),lim&&i==dig[pos]);
19     }
20     if(!lim) dp[pos][f-sum]=ans;
21     return ans;
22 }
23 int solve(int x){
24     int pos=0;
25     while(x){
26         dig[pos++]=x%10;
27         x/=10;
28     }
29     return dfs(pos-1,0,1);
30 }
31 int main(){
32     int t;
33     int kase=0;
34     scanf("%d",&t);
35     memset(dp,-1,sizeof(dp));
36     while(t--){
37         int a,b;
38         scanf("%d%d",&a,&b);
39         f=cal(a);
40         printf("Case #%d: %d
",++kase,solve(b));
41     }
42     return 0;
43 }
View Code

Round Numbers

 POJ - 3252 

题意:求区间[a,b]之间有多少个数的二进制形式中0的个数大于1的个数。

好像还可以用组合数搞

 1 #include <iostream>
 2 #include <cstdio>
 3 #include <cstring>
 4 using namespace std;
 5 int a,b;
 6 int dp[35][65];
 7 int bit[35];
 8 int dfs(int pos,int sta,int lead,int lim){
 9     if(pos==-1) return sta>=32;
10     if(!lim&&!lead&&dp[pos][sta]!=-1) return dp[pos][sta];
11     int up=lim?bit[pos]:1;
12     int ans=0;
13     for(int i=0;i<=up;i++){
14         if(lead&&i==0) ans+=dfs(pos-1,sta,lead,lim&&i==bit[pos]);
15         else ans+=dfs(pos-1,sta+(i==0?1:-1),lead&&i==0,lim&&i==bit[pos]);
16     }
17     if(!lim&&!lead) dp[pos][sta]=ans;
18     return  ans;
19 }
20 int solve(int x){
21     int pos=0;
22     while(x){
23         bit[pos++]=x&1;
24         x>>=1;
25     }
26     return dfs(pos-1,32,1,1);
27 }
28 
29 int main(){
30     memset(dp,-1,sizeof(dp));
31     while(scanf("%d%d",&a,&b)!=EOF){
32         printf("%d
",solve(b)-solve(a-1));
33     }
34     return 0;
35 }
View Code

吉哥系列故事——恨7不成妻

 HDU - 4507 

题意:求区间[a,b]中与7无关的数字的平方和。与7无关:数位中不含7,这个数不是7的倍数,各数位和不是7的倍数。

难点在求平方和。

例如求256和289的平方和 ,可以写成(200+56)2+(200+89)= 2002×2+2×200×(56+89)+(562+892),

即 base[pos]*i*base[pos]*i*temp.cnt+2*i*base[pos]*temp.sum+temp.qsum。当前位置为pos,当前位置放i这个数,temp是后面的位置的结果。

 1 #include <bits/stdc++.h>
 2 using namespace std;
 3 #define ll long long
 4 const int mod=1e9+7;
 5 
 6 struct Node{
 7     ll cnt;  //数字个数
 8     ll sum;  // 各数字之和
 9     ll qsum;  // 各数字的平方和
10     Node (ll cnt=0,ll sum=0,ll qsum=0):cnt(cnt),sum(sum),qsum(qsum){}
11 }dp[20][7][7];
12 int bit[20];
13 ll base[20];
14 
15 ll a,b;
16 
17 Node dfs(int pos,int pa,int pb,int lim){  //当前位置,数位之和除以7的余数,数字除以7的余数,上一位是否达到上限
18     if(pos==-1) return Node(pa&&pb,0,0);
19     if(!lim&&dp[pos][pa][pb].cnt!=-1) return dp[pos][pa][pb];
20     int up=lim?bit[pos]:9;
21     Node ans(0,0,0);
22     for(int i=0;i<=up;i++){
23         if(i!=7){
24             Node temp=dfs(pos-1,(pa+i)%7,(pb*10+i)%7,lim&&i==bit[pos]);
25             ans.cnt=(ans.cnt+temp.cnt)%mod;
26             ans.sum=(ans.sum+(((base[pos]*i)%mod*temp.cnt)%mod+temp.sum)%mod)%mod;
27             //平方和展开成三项
28             ans.qsum=(ans.qsum
29                       +base[pos]*i%mod*base[pos]*i%mod*temp.cnt%mod
30                       +2*i*base[pos]%mod*temp.sum%mod
31                       +temp.qsum)%mod;
32         }
33     }
34     if(!lim) dp[pos][pa][pb]=ans;
35     return ans;
36 }
37 
38 ll solve(ll x){
39     int pos=0;
40     while(x){
41         bit[pos++]=x%10;
42         x/=10;
43     }
44     Node ans=dfs(pos-1,0,0,1);
45     return  ans.qsum;
46 }
47 int main(){
48     int t;
49     base[0]=1;
50     for(int i=1;i<20;i++) base[i]=base[i-1]*10%mod;
51     memset(dp,-1,sizeof(dp));
52     scanf("%d",&t);
53     while(t--){
54         scanf("%lld%lld",&a,&b);
55         printf("%lld
",((solve(b)-solve(a-1))%mod+mod)%mod);
56     }
57     return 0;
58 }
View Code

K-th Nya Number

 HDU - 3943 

 题意:找到区间[p+1,q]之间的第k个Nya数。Nya数是指数位含有x个4和y个7的数。

二分法~,,因为Nya数的个数满足单调性,即在[0,d]中,d越大,Nya数越多。

dp记录的状态是剩余那些数位需要满足的条件,,这样才可以记忆化~

 1 #include <bits/stdc++.h>
 2 using namespace std;
 3 #define ll long long
 4 const int maxn=25;
 5 
 6 int bit[maxn];
 7 ll dp[maxn][maxn][maxn];
 8 ll p,q,x,y;
 9 ll k;
10 
11 ll dfs(int pos,int fa,int fb,int lim){
12     if(pos==-1) return fa==0&&fb==0;
13     if(fa<0||fb<0) return 0;
14     if(!lim&&dp[pos][fa][fb]!=-1) return dp[pos][fa][fb];
15     int up=lim?bit[pos]:9;
16     ll ans=0;
17     for(int i=0;i<=up;i++){
18         ans+=dfs(pos-1,fa-(i==4),fb-(i==7),lim&&i==bit[pos]);
19     }
20     if(!lim) dp[pos][fa][fb]=ans;
21     return ans;
22 }
23 ll solve(ll c){
24     int pos=0;
25     while(c){
26         bit[pos++]=c%10;
27         c/=10;
28     }
29     return dfs(pos-1,x,y,1);
30 }
31 
32 void get_k(){
33     ll c1=solve(p);
34     ll c2=solve(q);
35     if(k>c2-c1){
36         puts("Nya!");
37         return ;
38     }
39     k+=c1;
40     ll l=p+1,r=q;
41     while(l<=r){
42         ll m=(l+r)>>1;
43         if(solve(m)>=k) r=m-1;  //
44         else l=m+1;
45     }
46     printf("%lld
",l);
47 }
48 int main(){
49     int t,kase=0;
50     memset(dp,-1,sizeof(dp));
51     scanf("%d",&t);
52     while(t--){
53         printf("Case #%d:
",++kase);
54         scanf("%lld%lld%lld%lld",&p,&q,&x,&y);
55         int m;
56         scanf("%d",&m);
57         while(m--){
58             scanf("%lld",&k);
59             get_k();
60         }
61     }
62     return 0;
63 }
View Code

SNIBB

 HDU - 3271 

题意:操作1求区间[x,y]之间的数写成b进制形式后数位和等于m的有几个数。操作2求第k个 在区间[x,y]之间的数写成b进制形式后数位和等于m的数。

数位DP是做的多了就感觉都差不多了~

这个题有几点注意的地方,一是题目并没有说x一定小于y,要判断一下。二是二分答案的时候x+y可能会溢出导致超时要用long long 。

 1 #include <bits/stdc++.h>
 2 using namespace std;
 3 #define ll long long
 4 const int maxn=35;
 5 
 6 int dp[maxn][320];
 7 int bit[maxn];
 8 int x,y,b,m,k;
 9 
10 int dfs(int pos,int sum,int lim){
11     if(pos==-1) return sum==0;
12     if(sum<0) return 0;
13     if(!lim&&dp[pos][sum]!=-1) return dp[pos][sum];
14     int up=lim?bit[pos]:b-1;
15     int ans=0;
16     for(int i=0;i<=up;i++){
17         ans+=dfs(pos-1,sum-i,lim&i==bit[pos]);
18     }
19     if(!lim) dp[pos][sum]=ans;
20     return  ans;
21 }
22 
23 int solve(int c){
24     int pos=0;
25     while(c){
26         bit[pos++]=c%b;
27         c/=b;
28     }
29     return dfs(pos-1,m,1);
30 }
31 
32 int main(){
33     int c;
34     int kase=0;
35     while(scanf("%d",&c)!=EOF){
36         memset(dp,-1,sizeof(dp));
37         printf("Case %d:
",++kase);
38         if(c==1){
39             scanf("%d%d%d%d",&x,&y,&b,&m);
40             if(x>y) swap(x,y);
41             printf("%d
",solve(y)-solve(x-1));
42         }else{
43             scanf("%d%d%d%d%d",&x,&y,&b,&m,&k);
44             if(x>y) swap(x,y);
45             int c1=solve(x-1);
46             int c2=solve(y);
47             if(k>c2-c1){
48                 puts("Could not find the Number!");
49                 continue;
50             }
51             k+=c1;
52             while(x<=y){
53                 int m=((long long)x+y)>>1;
54                 if(solve(m)>=k) y=m-1;
55                 else x=m+1;
56             }
57             printf("%d
",x);
58         }
59     }
60     return 0;
61 }
View Code

Zero's Number

 HDU - 3967 

题意:在区间[a,b]内,将x分成两半,两半之和是k的倍数。问共有多少种分法。

也可以多开一维数组记录一下k,然后把memset放到外面,稍快一点。

 1 #include <bits/stdc++.h>
 2 using namespace std;
 3 #define ll long long
 4 
 5 ll dp[22][22][22][2]; //当前位置、分位点、余数、是否有前导零
 6 ll base[22];
 7 int bit[22];
 8 ll a,b;
 9 int k;
10 ll dfs(int pos,int id,int mod,int lead,int lim){
11     if(pos==-1) return mod?0:1;
12     if(!lim&&dp[pos][id][mod][lead]!=-1) return dp[pos][id][mod][lead];
13     int up=lim?bit[pos]:9;
14     ll ans=0;
15     for(int i=0;i<=up;i++){
16         if(pos>=id){
17             if((lead==0&&pos==id&&i==0)) continue;
18             ans+=dfs(pos-1,id,(i*base[pos-id]+mod)%k,lead||i,lim&&i==bit[pos]);
19         }else {
20             ans+=dfs(pos-1,id,(i*base[pos]+mod)%k,lead||i,lim&&i==bit[pos]);
21         }
22     }
23     if(!lim) dp[pos][id][mod][lead]=ans;
24     return ans;
25 }
26 ll solve(ll x){
27     int pos=0;
28     if(x<10) return 0;
29     while(x){
30         bit[pos++]=x%10;
31         x/=10;
32     }
33     ll ans=0;
34     for(int i=1;i<pos;i++)
35         ans+=dfs(pos-1,i,0,0,1);
36     return ans;
37 }
38 int main(){
39     base[0]=1;
40     for(int i=1;i<19;i++) base[i]=base[i-1]*10;
41     while(scanf("%lld%lld%d",&a,&b,&k)!=EOF){
42         memset(dp,-1,sizeof(dp));
43         printf("%lld
",solve(b)-solve(a-1));
44     }
45 }
View Code

Bi-peak Number

 HDU - 3565 

题意:问区间[a,b]内的双峰的数的最大数位之和。如121121就是双峰的数。

看来我还是只会做水题~复杂一点就不会了。。。

 1 #include <bits/stdc++.h>
 2 using namespace std;
 3 const int inf=0x3f3f3f3f;
 4 #define ull unsigned long long
 5 ull  a,b;
 6 int dp[22][10][7];
 7 int bit1[22],bit2[22];
 8 
 9 int dfs(int pos,int pre,int sta,int lim1,int lim2){
10     if(pos==-1) return sta==6?0:-inf;
11     if(!lim1&&!lim2&&dp[pos][pre][sta]!=-1) return dp[pos][pre][sta];
12     int down=lim1?bit1[pos]:0;
13     int up=lim2?bit2[pos]:9;
14     int ans=-inf;
15     for(int i=down;i<=up;i++){
16         int temp=0;
17         if(sta==0&&i){
18             temp=1;
19         }else if(sta==1){
20             if(i>pre) temp=2;
21             else temp=-1;
22         }else if(sta==2){
23             if(i>pre) temp=2;
24             else if(i<pre) temp=3;
25             else temp=-1;
26         }else if(sta==3){
27             if(i>pre) temp=4;
28             else if(i<pre) temp=3;
29             else {
30                 if(i) temp=4;
31                 else temp=-1;
32             }
33         }else if(sta==4){
34             if(i>pre) temp=5;
35             else temp=-1;
36         }else if(sta==5){
37             if(i<pre) temp=6;
38             else if(i>pre) temp=5;
39             else temp=-1;
40         }else if(sta==6){
41             if(i<pre) temp=6;
42             else temp=-1;
43         }
44         if(temp!=-1){
45             int res=dfs(pos-1,i,temp,lim1&&i==bit1[pos],lim2&&bit2[pos]==i);
46             ans=max(ans,res+i);
47         }
48     }
49     if(!lim1&&!lim2) dp[pos][pre][sta]=ans;
50     return  ans;
51 }
52 int solve(ull a,ull b){
53     int pos=0;
54     while(b){
55         bit1[pos]=a%10;
56         bit2[pos++]=b%10;
57         a/=10;b/=10;
58     }
59     return dfs(pos-1,0,0,1,1);
60 }
61 int main(){
62     int t;
63     int kase=0;
64     scanf("%d",&t);
65     memset(dp,-1,sizeof(dp));
66     while(t--){
67         scanf("%llu%llu",&a,&b);
68         printf("Case %d: %d
",++kase,max(0,solve(a,b)));
69     }
70 }
View Code

Investigating Div-Sum Property

 UVA - 11361 

题意:区间[a,b]中数位和和数字是k的倍数的数字有多少个。

 1 #include <bits/stdc++.h>
 2 using namespace std;
 3 #define ll long long
 4 const int maxn=10000;
 5 int dp[20][100][100];
 6 int bit[10];
 7 int base[10];
 8 int a,b;
 9 int k;
10 int dfs(int pos,int pa,int pb,int lim){
11     if(pos==-1) return pa==0&&pb==0;
12     if(!lim&&dp[pos][pa][pb]!=-1) return dp[pos][pa][pb];
13     ll ans=0;
14     int up=lim?bit[pos]:9;
15     for(int i=0;i<=up;i++){
16         ans+=dfs(pos-1,(pa+i)%k,(pb+i*base[pos])%k,lim&&i==bit[pos]);
17     }
18     if(!lim) dp[pos][pa][pb]=ans;
19     return ans;
20 }
21 int solve(int x){
22     int pos=0;
23     while(x){
24         bit[pos++]=x%10;
25         x/=10;
26     }
27     return dfs(pos-1,0,0,1);
28 }
29 int main(){
30     int t;
31     scanf("%d",&t);
32     base[0]=1;
33     for(int i=1;i<=10;i++) base[i]=base[i-1]*10;
34     while(t--){
35         memset(dp,-1,sizeof(dp));
36         scanf("%d%d%d",&a,&b,&k);
37         if(k>=100) puts("0");
38         else printf("%d
",solve(b)-solve(a-1));
39     }
40     return 0;
41 }
View Code

Amount of Degrees

 URAL - 1057 

Sorted bit sequence

 UVALive - 3675

Sequence

 SPOJ - BIGSEQ 

Sequence

Tickets

 SGU - 390 

Graduated Lexicographical Ordering

 ZOJ - 2599

原文地址:https://www.cnblogs.com/yijiull/p/7340016.html