数位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 }
同样的拆分 出每一位 左分支为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 }
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 }
与上一道题相比类似 不过有几个细节的问题需要注意 首先是第一个数不能够给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 }
五
题目链接: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 }
所以关于边界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 }
除了正常的树形法之外 还要考虑 前面的位置的数也有贡献 如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 }
八
题目链接: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 }

浙公网安备 33010602011771号