re:从零开始的数位dp
起源:唔,,前几天打cf,edu50那场被C题虐了,决定学学数位dp。(此文持续更新至9.19)
ps:我也什么都不会遇到一些胡话大家不要喷我啊。。。
数位dp问题:就是求在区间l到r上满足规定条件的数的个数。
ex1:hdu3555
题意:给你n,求从一到n中有多少个数不包含“49”。(t<=1e4,n<=2^63-1)
首先数位dp顾名思义就是对数位进行dp嘛,所以dp数组的第一维我们用来保存数字的位数,第二位我们用来判定当前位是否为4,
所以就是 dp[20][2]; 这个样子。 在这之前我们先考虑一下常规的搜索思路,一件非常显然的事情,在[1,1000]和[1001,2000]以及所有类似区间里符合要求的数都是一样的,这样我们就可以通过记忆化的方式来保存某些结果。
先给出solve函数
ll solve(ll num){
int k = ;//记录数位
while(num){
k++;
digit[k]=num%;
num/=;
}
return dfs(k,false,true);
}
这个很好理解嘛,保存这个数各位上的数字。然后我们就可以进行记忆化搜索了,
dp[20][2]:表示 1.有4的时候有几个含有49, 2.没有4的时候,有几个含有49。
ll dfs(int len,bool if4,bool limit){
//当前是第几位,上一位是否是4,上一位是否是上界
if(len==0)//统计完了直接返回1
return 1;
if(!limit&&dp[len][if4])//不是上界并且这种情况已经统计过
return dp[len][if4];
ll cnt=0,up_bound=(limit?digit[len]:9);//up_bound是当前位能满足的最大值,如果上一位是上界的话,当前位最大只能取到当前位的数字,如果不是,当前位可以从0取到9
for(int i=0;i<=up_bound;i++){
if(if4&&i==9)
continue;//上一位是4并且这一位是9,GG了啊
cnt+=dfs(len-1,i==4,limit&&i==up_bound);//上一位是上界的情况下我们才会考虑这一位是否是上界
}
if(!limit)//不是上界,属于通用的情况,我们进行赋值
dp[len][if4]=cnt;
return cnt;
}
最后结果差分一下就好。 ex2:hdu2089
和上道题几乎一样,条件是没有“4”并且没有“62”,
这时候我们掏出上一道题的板子了嘛肯定要,只需要在判断时加入一句话就行,在代码中加上注释了,就不做多解释了
#include <bits/stdc++.h>
using namespace std;
int n,m;
int digit[];
int dp[][];
int dfs(int len,bool if6, bool limit){
if(len==)
return ;
if(!limit&&dp[len][if6])
return dp[len][if6];
int cnt = ,up_bound = (limit?digit[len]:);
for(int i=;i<=up_bound;i++){
if(i==)//如果遇到四就直接GG
continue;
if(if6&&i==)
continue;
cnt+=dfs(len-,i==,limit&&i==up_bound);
}
if(!limit)
dp[len][if6]=cnt;
return cnt;
}
int solve(int num){
int k = ;//记录数位
while(num){
k++;
digit[k]=num%;
num/=;
}
return dfs(k,false,true);
} int main(){
while (scanf("%d%d",&n,&m)&&(n+m)) {
cout << solve(m) - solve(n - ) << endl;
}
}
ex3:codeforces1036C,也就是EDU50的C题嘛,一道非常简单的板子题,(问题是我当时还没听说过数位dp,,真的,,不然就可以骑学长了,,哭)
条件是 零的个数不大于3个。
很显然我们只要把dp数组的第二维开到3就可以了嘛,,然后就是套板子,, 我这里把0的情况单独拿出来了,因为1到9都可以一起考虑嘛
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
int digit[];
ll dp[][];
ll dfs(int len,int not0,bool limit){
if(len==)//完了
return ;
if(!limit&&dp[len][not0])//已经统计过
return dp[len][not0];//
ll cnt=,up_bound=(limit?digit[len]:);//
cnt+=dfs(len-,not0,limit&&digit[len]==);//
for(int i=;i<=up_bound;i++){
if(not0==)
continue;
cnt+=dfs(len-,not0+,limit&&i==up_bound);
}
if(!limit)
dp[len][not0]=cnt;
return cnt;
}
ll solve(ll num){
int k = ;//记录数位
while(num){
k++;
digit[k]=num%;
num/=;
}
return dfs(k,,true);
}
int t;ll l,r;
int main(){
ios::sync_with_stdio(false);
cin>>t;
while (t--){
cin>>l>>r;
cout<<(solve(r)-solve(l-))<<endl;
}
}
ex4:
hdu3652
条件:包括“13”并且能被13整除
首先我们想到用余数来分类嘛,然后结合最初的板子,开出来的dp数组就是这样的 dp[17][17][3];//分别是数位,余数,对于“13”的三种状态(包括1,包括13,啥都没有)
还有取模的那个地方需要稍微理解一下,剩下的也就是板子了
#include <bits/stdc++.h>
using namespace std;
int digit[];
int dp[][][];
int dfs(int len,int mod,int have,int limit){
if(len==)
return mod==&&have==;
if(!limit&&dp[len][mod][have])
return dp[len][mod][have];
int cnt = ,up_bound=(limit?digit[len]:);
for(int i=;i<=up_bound;i++){
int mod_ = (mod*+i)%;
int tmp = have;
if(have==&&i==)
tmp = ;
if(have==&&i!=)
tmp = ;
if(have==&&i==)
tmp = ;
cnt+=dfs(len-,mod_,tmp,limit&&i==up_bound);
}
if(!limit)
dp[len][mod][have] = cnt;
return cnt;
} int solve(int num){
int k = ;
while (num){
k++;
digit[k] = num%;
num/=;
}
return dfs(k,,,);
}
int n;
int main(){
ios::sync_with_stdio(false);
while (scanf("%d",&n)!=EOF){
// scanf("%d",&n);
cout<<solve(n)<<endl;
}
}
ex5: codeforces 55 d, 是上一道题略微进化版 条件“这个数能被他的每个非零位的最小公倍数整除”;
首先我们还是想到对模数来分类,这是非常显然的嘛,除此之外,我们需要保存“最小公倍数”,因为转移的时候必须要用到之前的,这样子我们开出来的数组是 dp[20][2520][2520],好恭喜你MLE on test 1,这时就需要一些奇淫技巧。我们会发现,lcm的个数非常有限吧,实际上只有48个,所以我们可以离散化嘛。然后就与上一题非常相似了,我们保存 “presum”和“prelcm”,进行记忆化搜索即可。
为什么可以%2520呢,
- 首先我们能够知道如果这个数能够整除它的每个数位上的数字,那么它一定能够整除他们的最小公倍数,是充要的。
- 那么我们定义状态dp[i][j][k]代表i位在任意组合下,得到的所有数位的数字的最小公倍数为j,且该数%2520为k的方案数。
- 我们可以知道任意多个1-9之间的数的公倍数最大不会超过2520,而且他们都是2520的约数,所以(一个数%2520)能够被该数所有数位的数字的最小公倍数整除,那么该数就能整除自己每个数位上的数字。
#include <cstdio>
using namespace std;
typedef long long ll;
int t;ll l,r;
const int mod = ;
ll dp[][mod][];
int ind[mod+];
int digit[];
void init(){
int cnt = ;
for(int i=;i<=mod;i++){
if(mod%i==)
ind[i]=cnt++;
}
} int gcd(int a, int b){
return b==?a:gcd(b,a%b);
} int lcm(int a, int b){
return a/gcd(a,b)*b;
}
ll dfs(int len,int presum,int prelcm,bool limit){
if(len==)
return presum%prelcm==;
if(!limit&&dp[len][presum][ind[prelcm]])
return dp[len][presum][ind[prelcm]];
ll cnt = ;int up_bound = limit?digit[len]:;
for(int i=;i<=up_bound;i++){
int nowsum = (presum*+i)%mod;
int nowlcm = prelcm;
if(i!=)
nowlcm = lcm(nowlcm,i);
cnt+=dfs(len-,nowsum,nowlcm,limit&&i==up_bound);
}
if(!limit)
dp[len][presum][ind[prelcm]] = cnt;
return cnt;
}
ll solve(ll num){
int k = ;
while (num){
k++;
digit[k]=num%;
num/=;
}
return dfs(k,,,);
} int main(){
init();
scanf("%d",&t);
while (t--){
scanf("%I64d%I64d",&l,&r);
printf("%I64d\n",solve(r)-solve(l-));
}
}
ex6:poj 3286 求区间里包含多少个零
我随手一写竟然过了。网上题解貌似没看到和我的写法一样的。所以讲的详细些,还是套板子,dfs里的四个参数分别表示 位数,零的个数,是否前导零,是否上界
然后就很简单了嘛。注意到l可以取到0,然后我们的dfs对于零来说是没有计算在内的,所以要加上1。
#include <iostream>
using namespace std;
typedef long long ll;
int digit[];
int dp[][];
ll l,r;
ll dfs(int len,int count,bool zero,bool limit){
if(len==)
return count;
if(!zero&&!limit&&dp[len][count])
return dp[len][count];
ll cnt=;int up_bound=limit?digit[len]:;
cnt+=dfs(len-,count+(zero?:),zero,limit&&digit[len]==);
for(int i=;i<=up_bound;i++){
cnt+=dfs(len-,count,false,limit&&i==up_bound);
}
if(!limit&&!zero)
dp[len][count]=cnt;
return cnt;
}
ll solve(ll num){
int k = ;
while (num){
k++;
digit[k]=num%;
num/=;
}
return dfs(k,,,);
}
int main(){
ios::sync_with_stdio(false);
while () {
cin >> l >> r;
if(l==-||r==-)
return ;
if (l == )
cout << solve(r) + << endl;
else
cout << solve(r) - solve(l - ) << endl;
}
}
ex7:poj2282&&bzoj1833&&luogu2602&&zjoi1010
求区间里包含“0,1,2,3,4,5,6,7,8,9”的个数分别是多少
和ex6一毛一样嘛,,一个很直观的思路就是我们把ex6跑十遍吧,嗯正解就是这样(囍的不行),我觉着求“1,2,3,4,5,6,7,8,9”还要比“0”简单咯,不用考虑前导0,然后把代码稍微一改就可以了。哦对了!他这个鬼输入竟然l还能大于r,所以要判断swap一下,,,我一开始测样例输出了一堆负数让我受到了很大的惊吓。。。
#include <iostream>
#include <cstring>
using namespace std;
typedef long long ll;
int digit[];
int dp[][][];
int ans[][];
ll l,r;
ll dfs0(int len,int count,bool zero,bool limit){
if(len==)
return count;
if(!zero&&!limit&&dp[len][count][])
return dp[len][count][];
ll cnt=;int up_bound=limit?digit[len]:;
cnt+=dfs0(len-,count+(zero?:),zero,limit&&digit[len]==);
for(int i=;i<=up_bound;i++){
cnt+=dfs0(len-,count,false,limit&&i==up_bound);
}
if(!limit&&!zero)
dp[len][count][]=cnt;
return cnt;
}
ll dfs(int len,int count,int num,bool limit){
if(len==)
return count;
if(!limit&&dp[len][count][num])
return dp[len][count][num];
ll cnt = ;int up_bound=limit?digit[len]:;
for(int i=;i<=up_bound;i++){
cnt+=dfs(len-,count+(i==num),num,limit&&i==up_bound);
}
if(!limit)
dp[len][count][num]=cnt;
return cnt;
}
void solve(ll num,int ind){
int k = ;
while (num){
k++;
digit[k]=num%;
num/=;
}
ans[][ind]=dfs0(k,,,);
for(int i=;i<=;i++)
ans[i][ind]=dfs(k,,i,true);
}
void init(){
memset(dp,, sizeof(dp));
memset(digit,, sizeof(digit));
}
int main(){
ios::sync_with_stdio(false);
while (cin>>l>>r&&l&&r) {
if(l>r)
swap(l,r);
init();
solve(r,);
init();
solve(l-,);
for(int i=;i<;i++)
cout<<ans[i][]-ans[i][]<<" ";
cout<<endl;
}
}
ex8:8102上海大都会赛J题
能被各位数字和整除 数据范围(N<=1e12)
首先我们应该会求 “被1整除”,“被二整除”,“被三整除”这样子的吧,然后神妙的战法就出现了,我们可以枚举各位数字和,,,12*9=128,也就是跑100来遍就行了吧。。。
初始化要初始化成-1,因为可能有很多状态本身就没有符合条件的数。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
int t;ll n;
int digit[];
ll dp[][][];//第i位,之前数位之和位j,对某个mod余数为k的满足条件的个数
int nowsum;
ll dfs(int len, int sum,int mod,bool limit){
if(len==)
return (sum==nowsum&&mod==);
if(!limit&&dp[len][sum][mod]!=-)
return dp[len][sum][mod];
ll cnt = ;int up_bound=limit?digit[len]:;
for(int i=;i<=up_bound;i++){
if(sum+i>nowsum)
break;
cnt+=dfs(len-,sum+i,(mod*+i)%nowsum,limit&&i==digit[len]);
}
if(!limit)
dp[len][sum][mod]=cnt;
return cnt;
}
ll solve(ll num) {
int k = ;
while (num) {
k++;
digit[k] = num % ;
num /= ;
}
ll res = ;
for(int i=;i<=*k;i++) {
nowsum = i;
memset(dp,-, sizeof(dp));
res += dfs(k,,, true);
}
return res;
} int main(){
ios::sync_with_stdio(false);
cin>>t;
int cas = ;
while (t--){
cin>>n;
cout<<"Case "<<++cas<<": "<<solve(n)<<endl;
}
return ;
}
re:从零开始的数位dp的更多相关文章
- 【BZOJ1662】[Usaco2006 Nov]Round Numbers 圆环数 数位DP
[BZOJ1662][Usaco2006 Nov]Round Numbers 圆环数 Description 正如你所知,奶牛们没有手指以至于不能玩"石头剪刀布"来任意地决定例如谁 ...
- bzoj1026数位dp
基础的数位dp 但是ce了一发,(abs难道不是cmath里的吗?改成bits/stdc++.h就过了) #include <bits/stdc++.h> using namespace ...
- uva12063数位dp
辣鸡军训毁我青春!!! 因为在军训,导致很长时间都只能看书yy题目,而不能溜到机房鏼题 于是在猫大的帮助下我发现这道习题是数位dp 然后想起之前讲dp的时候一直在补作业所以没怎么写,然后就试了试 果然 ...
- HDU2089 不要62[数位DP]
不要62 Time Limit: 1000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others)Total Submis ...
- 数位DP GYM 100827 E Hill Number
题目链接 题意:判断小于n的数字中,数位从高到低成上升再下降的趋势的数字的个数 分析:简单的数位DP,保存前一位的数字,注意临界点的处理,都是套路. #include <bits/stdc++. ...
- 数位dp总结
由简单到稍微难点. 从网上搜了10到数位dp的题目,有几道还是很难想到的,前几道基本都是模板题,供入门用. 点开即可看题解. hdu3555 Bomb hdu3652 B-number hdu2089 ...
- 数位DP入门
HDU 2089 不要62 DESC: 问l, r范围内的没有4和相邻62的数有多少个. #include <stdio.h> #include <string.h> #inc ...
- 数位DP之奥义
恩是的没错数位DP的奥义就是一个简练的dfs模板 int dfs(int position, int condition, bool boundary) { ) return (condition ? ...
- 浅谈数位DP
在了解数位dp之前,先来看一个问题: 例1.求a~b中不包含49的数的个数. 0 < a.b < 2*10^9 注意到n的数据范围非常大,暴力求解是不可能的,考虑dp,如果直接记录下数字, ...
随机推荐
- ASP.NET微信公众号获取AccessToken
access_token是公众号的全局唯一接口调用凭据,公众号调用各接口时都需使用access_token.开发者需要进行妥善保存.access_token的存储至少要保留512个字符空间.acces ...
- 初始化bootstrap treeview树节点
最近在做启明星图库时,使用了Jquery Bootstrap Treeview插件.但是,遇到了一个初始化的问题.先看效果如下: 当用户打开图库时,左边分类第一个类别是“所有分类”,默认需要选中. ...
- D3
D3.js是一个JavaScript库,它可以通过数据来操作文档.D3可以通过使用HTML.SVG和CSS把数据鲜活形象地展现出来.D3严格遵循Web标准,因而可以让你的程序轻松兼容现代主流浏览器并避 ...
- Exchange Online Mailbox Restoration
User Account is already deleted in AD.User Mailbox is already deleted in Exchange. 1. Connect to Exc ...
- JSONArray数据转换成java List
1.后台接收json数组转成封装实体类的List: package no.integrasco.ingentia.news.qaedition; public class Person { priva ...
- [CSS] Useful CSS tool for Web designer and developer
1. Color Picker (Chrome) You might know how to use color picker in Chrome, recently there is a featu ...
- Django 数据表更改
Django 数据表更改 « Django 开发内容管理系统(第四天) Django 后台 » 我们设计数据库的时候,早期设计完后,后期会发现不完善,要对数据表进行更改,这时候就要用到本节的知识. D ...
- Win10系统的SurfacePro4如何重装系统-1 SurfacePro专用的PE
下载SurfacePro专用的PE(普通的PE可能不支持触摸屏操作,甚至没法启动Surface,所以务必要重新制作PE),下面提供百度云下载地址,下载之后,双击EXE,会进行检测 链接:https:/ ...
- IDEA修改JDK(全)
https://www.cnblogs.com/hkgov/p/8074085.html 解决:javac: 无效的目标发行版: 1.8 解决:项目JDK版本不对 解决:Jar包问题 1," ...
- Python之数学题目练习
首先,下面的题目来自我的大学同学的分享,他用数学证明,我用编程计算机发现了答案. 他的数学推理: 然后下面是我的Python代码: #coding=utf-8 # 井的高度 well_hegith = ...