数位dp总结 ###K ###K //K

数位dp题目总结

 

 一

题目链接:https://vjudge.net/problem/URAL-1057

题意:求[l,r] 区间内满足条件的整数个数 这个数恰好等于K个不相等的B的整数次幂的和

 1 #include<bits/stdc++.h>
 2 #define ll long long
 3 #define pb push_back
 4 using namespace std;
 5 const int maxn=2e5+10;
 6 const int mod=1e9+7;
 7 
 8 int f[50][50];
 9 int k,b;
10 
11 int dp(int n)
12 {
13     if(!n) return 0; //判边界
14     vector<int>num; //记录每一位是什么
15     while(n) num.pb(n%b),n/=b;//转换成b进制
16 
17     int res=0;
18     int last=0;
19     int len=num.size();
20     for(int i=len-1;i>=0;i--)
21     {
22         int x=num[i];
23         if(x)//计算左边的分支 如果是0的话左边就没有分支
24         {
25             //先枚举当前这一位是0
26             res+=f[i][k-last];
27 
28             //在枚举当前这一位是1
29             if(x>1)//确保在左分支 x就必须大于1
30             {
31                 if(k-last-1>=0)
32                 res+=f[i][k-last-1];
33                 break;
34                 // 当前这一位填x 导致右分支没有了
35             }
36             else
37             {
38                 last++;// 右分支填1
39                 if(last>k)
40                     break;
41             }
42         }
43         if(!i&&last==k) res++; //特判最后一个右分支
44     }
45     return res;
46 }
47 
48 int main()
49 {
50     ios::sync_with_stdio(0);
51     cin.tie(0);
52     for(int i=0;i<50;i++)
53     {
54         for(int j=0;j<=i;j++)
55         {
56             if(j==0)
57                 f[i][j]=1;
58             else
59                 f[i][j]=f[i-1][j-1]+f[i-1][j];
60         }
61     }
62     int l,r;
63     cin>>l>>r>>k>>b;
64     cout<<dp(r)-dp(l-1)<<'\n';
65 
66 
67 
68 
69 }
View Code

同样的拆分 出每一位 左分支为0~ x-1 右分支填x  

 

 二

题目链接:https://vjudge.net/problem/LibreOJ-10164

 1 #include<bits/stdc++.h>
 2 #define ll long long
 3 #define pb push_back
 4 using namespace std;
 5 const int maxn=2e5+10;
 6 const int mod=1e9+7;
 7 ll dp[15][15];// 有i位最高位为j的不降数的个数
 8 
 9 void init()
10 {
11     for(int i=0;i<=9;i++)
12         dp[1][i]=1;
13     for(int i=2;i<=12;i++)
14     {
15         for(int j=0;j<=9;j++)
16         {
17             for(int k=j;k<=9;k++)
18                 dp[i][j]+=dp[i-1][k];
19         }
20     }
21 }
22 
23 int solve(int n)
24 {
25     if(!n) return 1;
26     vector<int>num;
27     while(n) num.pb(n%10),n/=10;
28 
29     int ans=0;
30     int last=0;//上一个数
31 
32     int len=num.size();
33     for(int i=len-1;i>=0;i--)
34     {
35         int x=num[i];
36         for(int j=last;j<x;j++)
37         {
38             ans+=dp[i+1][j];
39         }
40         if(x<last)
41             break;
42         last=x;
43 
44         if(!i) ans++;
45 
46     }
47     return ans;
48 }
49 
50 int main()
51 {
52     ios::sync_with_stdio(0);
53     cin.tie(0);
54     init();
55     int a,b;
56     while(cin>>a>>b)
57     {
58         cout<<solve(b)-solve(a-1)<<'\n';
59     }
60 
61 
62 
63 
64 }
View Code

dp[i][j] 为有i位最高位为j的不降数的数量 其余的同上树形模拟法

 

题目链接:https://www.luogu.com.cn/problem/P2657

 1 #include<bits/stdc++.h>
 2 #define ll long long
 3 #define pb push_back
 4 using namespace std;
 5 const int maxn=2e5+10;
 6 const int mod=1e9+7;
 7 ll dp[15][15];// 有i位最高位为j的个数
 8 
 9 void init()
10 {
11     for(int i=0;i<=9;i++)
12         dp[1][i]=1;
13     for(int i=2;i<=12;i++)
14     {
15         for(int j=0;j<=9;j++)
16         {
17             for(int k=0;k<=9;k++)
18             {
19                 if(abs(k-j)<2)
20                     continue;
21                 dp[i][j]+=dp[i-1][k];
22             }
23         }
24     }
25 }
26 
27 int solve(int n)
28 {
29     if(!n) return 0;
30 
31     vector<int>num;
32     while(n) num.pb(n%10),n/=10;
33     int len=num.size();
34     int ans=0;
35     int last=-10;
36 
37     for(int i=len-1;i>=0;i--)
38     {
39         int x=num[i];
40         for(int j=(i==len-1?1:0);j<x;j++)
41         {
42             if(abs(j-last)<2)
43                 continue;
44             ans+=dp[i+1][j];
45         }
46         if(abs(last-x)<2)
47             break;
48         last=x;
49         if(!i) ans++;
50     }
51     for(int i=1;i<len;i++)
52         for(int j=1;j<=9;j++)
53             ans+=dp[i][j];
54     return ans;
55 }
56 
57 
58 int main()
59 {
60     ios::sync_with_stdio(0);
61     cin.tie(0);
62     init();
63     int a,b;
64     cin>>a>>b;
65     cout<<solve(b)-solve(a-1)<<'\n';
66 
67 
68 
69 
70 }
View Code

与上一道题相比类似 不过有几个细节的问题需要注意  首先是第一个数不能够给0,因为如果给0的话 会在后续的判断中

发生这样的情况,0135这种,我们把0记为了last ,所以这样是不合法的,但其实0135=135是合法的

而上一道题 则是因为0 1 3 5  和1 3 5 都是不降的序列所以没有影响

这样会漏掉情况,所以我们需要让第一位是1,并且特殊处理第一位为0的情况 即把所有有前导零的数单独累加

还有边界为0 返回0的问题, 虽然预处理的时候dp[1][0]=1 但是因为我们最高位是从1开始, 所以并没有能够将0的贡献算进去

所以边界应该规定为0  

 

题目链接:https://vjudge.net/problem/LibreOJ-10166

dp[i][j][k] 为有i位最高位为j且mod m为k的数量  注意取模的时候确保得到的数是正数(即0~m-1),

然后last记录的是上面的值的和的取模  还有就是无效状态不用置为-1 0即可代表没有, 因为dp属性是数量不是max/min

 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 const int maxn=2e5+10;
 4 const int mod=998244353;
 5 #define ll long long
 6 #define pb push_back
 7 
 8 ll dp[15][15][111];// i位最高位为j模数为k的数量
 9 int a,b,m;
10 
11 void init()
12 {
13     memset(dp,0,sizeof(dp));
14     for(int i=0;i<=9;i++)
15         dp[1][i][i%m]=1;
16     for(int i=2;i<=12;i++)
17     {
18         for(int j=0;j<=9;j++)
19         {
20             for(int k=0;k<m;k++)
21             {
22                 int now=(k-j%m+m)%m;
23                 for(int x=0;x<=9;x++)
24                 {
25                     if(dp[i-1][x][now]==-1)
26                         continue;
27                     dp[i][j][k]+=dp[i-1][x][now];
28                 }
29             }
30         }
31     }
32 
33 }
34 
35 int solve(int n)
36 {
37     if(!n) return 1;
38     vector<int>num;
39     while(n) num.pb(n%10),n/=10;
40 
41     int len=num.size();
42     int ans=0;
43     int last=0;
44 
45     for(int i=len-1;i>=0;i--)
46     {
47         int x=num[i];
48         for(int j=0;j<x;j++)
49         {
50             int k=(-last+m)%m;
51             ans+=dp[i+1][j][k];
52         }
53         last+=x;
54         last%=m;
55 
56         if(!i&&last==0) ans++;
57     }
58     return ans;
59 }
60 
61 
62 
63 int main()
64 {
65     ios::sync_with_stdio(0);
66     cin.tie(0);
67     while(cin>>a>>b>>m)
68     {
69         init();
70         cout<<solve(b)-solve(a-1)<<'\n';
71     }
72 
73 
74 
75 
76 
77 
78 
79 
80 
81 }
View Code

 

 

 题目链接:https://vjudge.net/problem/LibreOJ-10167  代码细节已注释

 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 const int maxn=1e6+10;
 4 const int mod=998244353;
 5 #define ll long long
 6 #define pb push_back
 7 
 8 int dp[10][10];// 有i位且最高位为j的数量
 9 
10 void init()
11 {
12     for(int i=0;i<=9;i++)//0是一定需要的 因为如20这种靠0转移过来
13     {
14         dp[1][i]=1;
15     }
16     dp[1][4]=0;
17     for(int i=2;i<10;i++)
18     {
19         for(int j=0;j<=9;j++)
20         {
21             if(j==4)
22                 continue;
23             for(int k=0;k<=9;k++)
24             {
25                 if((j==6&&k==2))
26                     continue;
27                 dp[i][j]+=dp[i-1][k];
28             }
29         }
30     }
31 }
32 
33 int solve(int n)
34 {
35     if(!n) return 1; //因为预处理的时候规定了0算一个数
36     int ans=0;
37     int last=0;
38     vector<int>num;
39     while(n) num.pb(n%10),n/=10;
40     int len=num.size();
41     for(int i=len-1;i>=0;i--)
42     {
43         int x=num[i];
44         for(int j=0;j<x;j++)
45         {
46             if((last==6&&j==2))
47                 continue;
48             ans+=dp[i+1][j];
49         }
50         if((x==2&&last==6)||x==4)
51             break;
52         if(!i)
53             ans++;
54         last=x;
55     }
56     return ans;
57 }
58 
59 
60 int main()
61 {
62     ios::sync_with_stdio(0);
63     cin.tie(0);
64     int n,m;
65     init();
66     while(cin>>n>>m)
67     {
68         if(n==0&&m==0)
69             break;
70         cout<<solve(m)-solve(n-1)<<'\n';
71     }
72 
73 
74 
75 
76 
77 
78 }
View Code

 所以关于边界0的问题  如果实际solve中要加到0的贡献 那么边界就置为1 否则就置为0  而dp[i][j] 的时候dp[1][0]是肯定要初始化为1的

 

 

六 

题目链接:https://www.acwing.com/problem/content/description/340/

  1 #include<bits/stdc++.h>
  2 using namespace std;
  3 const int maxn=1e6+10;
  4 const int mod=998244353;
  5 #define ll long long
  6 #define pb push_back
  7 
  8 ll dp[12][12][12];//有i位最高位为j含数k的个数
  9 ll ten[12];
 10 
 11 void init()
 12 {
 13     ten[0]=1;
 14     for(int i=1;i<=10;i++)
 15     {
 16         ten[i]=ten[i-1]*10;
 17     }
 18 
 19     for(int i=0;i<=9;i++)
 20         dp[1][i][i]=1;
 21     for(int i=2;i<=10;i++)
 22     {
 23         for(int j=0;j<=9;j++)
 24         {
 25             for(int k=0;k<=9;k++)
 26             {
 27                 if(j==k)
 28                     dp[i][j][k]+=ten[i-1];//以j为最高位的所有数都合法
 29                 for(int x=0;x<=9;x++)
 30                 {
 31                     dp[i][j][k]+=dp[i-1][x][k];
 32                 }
 33             }
 34         }
 35 
 36     }
 37 
 38 }
 39 
 40 ll solve(ll n,ll u)
 41 {
 42     if(!n)
 43     {
 44         return u==0?1:0;
 45     }
 46     ll ans=0;
 47     ll last=0;
 48     vector<int>num;
 49     while(n) num.pb(n%10),n/=10;
 50     int len=num.size();
 51     for(int i=len-1;i>=0;i--)
 52     {
 53         int x=num[i];
 54         for(int j=(i==len-1);j<x;j++)
 55         {
 56             ans+=dp[i+1][j][u];
 57         }
 58         ans+=last*x*ten[i];
 59         if(x==u)
 60             last++;
 61         if(!i)
 62             ans+=last;
 63     }
 64     for(int i=1;i<len;i++)
 65     {
 66         for(int j=(i!=1);j<=9;j++)
 67         {
 68             ans+=dp[i][j][u];
 69         }
 70     }
 71     return ans;
 72 }
 73 
 74 
 75 
 76 int main()
 77 {
 78     ios::sync_with_stdio(0);
 79     cin.tie(0);
 80     int a,b;
 81     init();
 82     while(cin>>a>>b)
 83     {
 84         if(!a&&!b)
 85             break;
 86         if(a>b)
 87             swap(a,b);
 88         for(int i=0;i<=9;i++)
 89         {
 90             if(i!=0)
 91                 cout<<" ";
 92             cout<<solve(b,i)-solve(a-1,i);
 93         }
 94         cout<<'\n';
 95     }
 96 
 97 
 98 
 99 
100 }
View Code

除了正常的树形法之外 还要考虑 前面的位置的数也有贡献 如3350  走到5的时候 前面的两个3 能贡献50次 (0~50) 

所以用last记录前面的u的出现的次数,要和前面的题区别开来

并且第一位也不是0开始,要从1开始, 同时最后单独加的时候 要特殊处理0 即j=(i!=1)

dp预处理的过程也要区别开之前的题目,每次j为最高位的时候 所有的数都能贡献1 如 9XXXXX  从90000到99999 第一个9贡献1万次

 

 

题目链接:https://codeforces.ml/gym/102832/problem/D

思路:  只是找每个数 的二进制中i个1  就是贡献 c^i  考虑数位dp 用二进制直接写即可, 时间复杂度 o(n^2) 因为中间要枚举有多少个1

 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 const int maxn=2e6+10;
 4 const int mod=1e9+7;
 5 #define ll long long
 6 #define pb push_back
 7 ll dp[3010][3010];
 8 
 9 ll power(ll base,ll n)
10 {
11     ll r=1;
12     while(n)
13     {
14         if(n%2) r=r*base%mod;
15         base=base*base%mod;
16         n/=2;
17     }
18     return r;
19 }
20 
21 string s;
22 ll c;
23 ll solve()
24 {
25     ll ans=0;
26     ll last=0;
27     int n=s.size();
28     for(int i=0;i<n;i++)
29     {
30         if(s[i]=='1')
31         {
32             ll sum=0;
33             int num=n-i-1;
34             for(int j=0;j<=num;j++)
35             {
36                 sum+=dp[num][j]*power(c,j+last)%mod;
37                 sum%=mod;
38             }
39             ans+=sum;
40             ans%=mod;
41             last++;
42         }
43         if(i==n-1)
44             ans+=power(c,last);
45         ans%=mod;
46     }
47     return ans;
48 }
49 
50 
51 
52 int main()
53 {
54     ios::sync_with_stdio(0);
55     cin.tie(0);
56     for(int i=0;i<3005;i++)
57     {
58         for(int j=0;j<=i;j++)
59         {
60             if(j==0)
61                 dp[i][j]=1;
62             else
63                 dp[i][j]=(dp[i-1][j-1]+dp[i-1][j])%mod;
64         }
65     }
66     //cout<<dp[5][2]<<'\n';
67     cin>>s>>c;
68     cout<<solve()<<'\n';
69 
70 
71 
72 
73 }
View Code

 

 

题目链接:https://ac.nowcoder.com/acm/contest/10746/A

思路: 数位dp  化成二进制  每次如果a的二进制不是1  那么x 剩下的位置都可以选择放和不放, 0这样也会被算入贡献

所以最后的答案需要减1

 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 const int maxn=2e6+10;
 4 const int mod=1e8;
 5 #define ll long long
 6 #define pb push_back
 7 
 8 int two[40];
 9 int suf[40];
10 int vis[40];
11 
12 int solve(int n)
13 {
14     int ans=0;
15     vector<int>num;
16     while(n) num.pb(n%2),n/=2;
17     int len=num.size();
18     for(int i=0;i<len;i++)
19     {
20         if(i!=0)
21         suf[i]=suf[i-1];
22         if(vis[i])
23             suf[i]++;
24     }
25     /*for(int i=len-1;i>=0;i--)
26     {
27         cout<<suf[i]<<" debug"<<'\n';
28     }*/
29     for(int i=len-1;i>=0;i--)
30     {
31         if(num[i]==1)
32         {
33             if(i==0)
34             {
35                 ans++;
36             }
37             else
38             {
39                 int shu=i-suf[i-1];
40                 ans+=two[shu];
41             }
42         }
43         //cout<<suf[r+1]<<" he"<<'\n';
44         if(vis[i]&&num[i]==1)
45             break;
46         if(!i)
47             ans++;
48     }
49     return ans;
50 }
51 
52 int main()
53 {
54     ios::sync_with_stdio(0);
55     cin.tie(0);
56     int k=1;
57     for(int i=0;i<30;i++)
58     {
59         two[i]=k;
60         k*=2;
61     }
62     int t;
63     cin>>t;
64     while(t--)
65     {
66         int a,x;
67        cin>>a>>x;
68        memset(suf,0,sizeof(suf));
69        memset(vis,0,sizeof(vis));
70        for(int i=0;i<30;i++)
71        {
72            if((a>>i)&1)
73             vis[i]=1;
74        }
75         cout<<solve(x)-1<<'\n';
76 
77     }
78 
79 
80 
81 
82 
83 
84 
85 }
View Code

 

posted @ 2021-01-03 20:35  canwinfor  阅读(55)  评论(0)    收藏  举报