数位dp & 热身训练7
数位dp
数位dp是一种计数用的dp,一般就是要统计一段区间$[L,R]$内,满足一定条件的数的个数,或者各个数位的个数。
数位dp使得暴力枚举变为满足一定状态的记忆化,更加优秀。
数位dp常常会考虑以下问题:
1.前导零的处理$lead$
2.枚举的上界$limit$
3.得到答案的条件
一般数位dp的模板
#include<bits/stdc++.h>
using namespace std;
#define re register int
#define int long long int num[20], dp[20][...];
int dp(int pos, 状态, int limit, int lead)
{
if(!pos) return 得到答案的条件;
int &ans = dp[pos][状态];
if(!limit && !lead && ~ans) return ans;
int ret = 0;
int up = limit ? num[pos] : 9;
for(re i=0;i<=up;++i)
{
if(i==0 && lead) ret += DFS(pos-1,..., limit && i == up, 1);
else ret += DFS(pos-1,..., limit && i == up, 0);
}
return (limit || lead) ? ret : ans = ret;
}
int solve(int x)
{
int len = 0;
while(x) num[++len] = x % 10, x /= 10;
return DFS(len, ״̬ , 1, 1);
}
signed main()
{
memset(dp, -1, sizeof dp);
int T;
scanf("%lld",&T);
while(T--)
{
int x, y;
scanf("%lld%lld",&x,&y);
printf("%lld\n", solve(y) - solve(x-1));
}
return 0;
}
实战篇:
例题一:Bomb
给一个数字N,求1~N有多少个数字的序列包含“49”子序列。
分析:dp状态:1.当前有无“49”;2.前一位是否位“4”;
#include<bits/stdc++.h>
using namespace std;
#define re register int
#define int long long int dp[20][2][2], num[20];
int dfs(int pos, int lst, int cnt, int limit)
{
if(pos == 0) return cnt;
int &ans = dp[pos][lst][cnt];
if(!limit && ~ans) return dp[pos][lst][cnt];
int up = limit ? num[pos] : 9, ret = 0; for(re i=0;i<=up;++i)
{
if(i == 9 && lst) ret += dfs(pos-1, 0, 1, limit && i == up);
else if(i == 4) ret += dfs(pos-1, 1, cnt, limit && i == up);
else ret += dfs(pos-1, 0, cnt, limit && i == up);
}
return limit ? ret : ans = ret;
}
void work()
{
memset(dp, -1, sizeof(dp));
int n, len=0; scanf("%lld",&n);
while(n) num[++len] = n % 10, n /= 10;
printf("%lld\n", dfs(len, 0, 0, 1));
}
signed main()
{
int T;
scanf("%lld",&T);
while(T--)work();
return 0;
}
例题二:数字0-9的数量
给出一段区间a-b,统计这个区间内0-9出现的次数。
分析:这道题统计“0”的个数时,需要考虑前导零!状态:当前有多少个所需数字。
#include<bits/stdc++.h>
using namespace std;
#define re register int
#define int long long int dp[20][20], num[20];
int dfs(int pos, int cnt, int id, int limit)
{
if(!pos)return cnt;
int &ans = dp[pos][cnt];
if(!limit && ~ans) return ans;
int up = limit ? num[pos] : 9;
int ret = 0;
for(re i=0;i<=up;++i)
ret += dfs(pos-1, cnt+(i==id), id, limit && i == up);
return limit ? ret : ans = ret;
} int DFS(int pos, int cnt, int limit, int lead)
{
if(!pos)return cnt;
int &ans = dp[pos][cnt];
if(!limit && !lead && ~ans) return ans;
int up = limit ? num[pos] : 9;
int ret = 0;
for(re i=0;i<=up;++i)
ret += DFS(pos-1, cnt + (!lead && i == 0), limit && i == up, lead && i == 0);
return limit || lead ? ret : ans = ret;
}
int Ans[10];
signed main()
{
int a, b;
scanf("%lld%lld",&a,&b); a --;
int len = 0; while(a) num[++len] = a % 10, a /= 10;
for(re i=1;i<=9;++i) memset(dp, -1, sizeof dp), Ans[i] -= dfs(len, 0, i, 1);
memset(dp, -1, sizeof dp), Ans[0] -= DFS(len, 0, 1, 1); len = 0; while(b) num[++len] = b % 10, b /= 10;
for(re i=1;i<=9;++i) memset(dp, -1, sizeof(dp)), Ans[i] += dfs(len, 0, i, 1);
memset(dp, -1, sizeof dp), Ans[0] += DFS(len, 0, 1, 1); for(re i=0;i<=9;++i)printf("%lld\n", Ans[i]);
return 0;
}
例题三:幸运数
求一段区间内,满足:1.数位之和为质数;2.数位的平方和为质数。
分析:状态:1.数位和;2.数位平方和。
#include<bits/stdc++.h>
using namespace std;
#define re register int
#define LL long long int lsp[1500], prm[1500], total;
void euler(int x)
{
lsp[1] = 1;
for(re i=2;i<=x;++i)
{
if(!lsp[i])prm[++total]=i;
for(re j=1;j<=total && prm[j]*i<=x;++j)
{
lsp[prm[j]*i] = 1;
if(i % prm[j] == 0) break;
}
}
} int num[20];
LL dp[20][162][1458];
LL dfs(int pos, int a, int b, int limit)
{
if(!pos)return !lsp[a] && !lsp[b];
LL &ans = dp[pos][a][b];
if(!limit && ~ans) return ans;
LL ret = 0;
int up = limit ? num[pos] : 9;
for(re i=0;i<=up;++i)
ret += dfs(pos-1, a+i, b+i*i, limit && i == up);
return limit ? ret : ans = ret;
}
LL solve(LL a)
{
int len = 0;
while(a) num[++len] = a % 10, a /= 10;
return dfs(len, 0, 0, 1);
}
signed main()
{
euler(1500);
memset(dp, -1, sizeof dp);
int T;
scanf("%d",&T);
while(T--)
{
LL a, b;
scanf("%lld%lld",&a,&b);
printf("%lld\n", solve(b) - solve(a-1));
}
return 0;
}
例题四:F(x)
我们定义十进制数x的权值 f(x) = a(n)*2^(n-1) + a(n-1)*2^(n-2)+...+ a(2)*2+ a(1)*1,
a(i)表示十进制数x中第i位的数字 题目给出a,b,求出0-b有多少个权值不大于f(a)的数。 数据组数T≤10000.
分析:
这道题就很有意思了!
我们先定义一个状态 $dp[pos][val]$表示$0$到$pos$位未确定,$len$到$pos+1$位权值为$val$。
这样我们确实可以推dp:
if(!pos) return val<=f(a);
else dp[pos][val] += dp[pos-1][val+x*(1<<pos)]; 1<=x<=9
但值得注意的是,f(a)在不同询问中,值不同,这样dp无法保留数据,会TLE。
因此,我们将状态变一下,val表示后pos位最多还可以有多少权值。
if(!pos) return val==0;
else dp[pos][val] += dp[pos-1][val+x*(1<<pos)]; 1<=x<=9
#include<bits/stdc++.h>
using namespace std;
#define re register int
#define int long long int g[20]={0,1,2,4,8,16,32,64,128,256,512};
int dp[20][5000], num[20];
int DFS(int pos, int v, int limit)
{
if(v < 0) return 0;
if(!pos) return 1;
int &ans = dp[pos][v];
if(!limit && ~ans) return ans;
int ret = 0, up = limit ? num[pos] : 9;
for(re i=0;i<=up;++i)
ret += DFS(pos-1, v-i*g[pos], limit && i == up);
return limit ? ret : ans = ret;
}
int solve()
{
int a, b;
scanf("%lld%lld",&a,&b); int mx = 0, len = 0;
while(a) mx += a % 10 * g[++len], a /= 10;
len = 0;
while(b) num[++len] = b % 10, b /= 10;
return DFS(len, mx, 1);
}
signed main()
{
memset(dp, -1, sizeof(dp));
int T;
scanf("%lld",&T);
for(re cas=1;cas<=T;++cas)
printf("Case #%lld: %lld\n", cas, solve());
return 0;
}
例题四:完美数
如果一个数能够被组成它的各个非0数字整除,则称它是完美数。
例如:1-9都是完美数,10,11,12,101都是完美数,但是13就不是完美数(因为13不能被数字3整除)。
现在给定正整数x,y,求x和y之间(包含x和y的闭区间)共有多少完美数。
分析:
首先一个状压S,表示哪些数字出现过,用于最终判断答案能否被出现过的数整除,O(29)
然后便是要分别记录当前数与{1,2,3,4,5,6,7,8,9}的模。但这样是O(9!),不行。
想一想,我们其实可以不记录所有的模数,例如2的余数可以通过4来算。
于是,我们只需要记录当前数对于{1,2,3,...,8,9}的$lcm=2520$的余数,即可O(2520)来记录当前数。
但这样还不够,我们还需要优化一下。
我们状压记录数字是否出现,为的就是最后判断答案能否贡献。
其实,判断能否被所有出现的数整除,还有一种办法:
判断当前数能否被所有出现的数的lcm最小公倍数整除!
例如:判断x,出现过的数有2,7,那么只需要判断x%14即可。
打表可以知道,1到9任意数字的lcm只有不到50种,我们的状态便优化成O(50*2520*20)。极其优秀!
综上,dp状态:1.$pos$;2.$lcm$,出现过的数的最小公倍数;3.$val$,当前数除以2520的余数。
#include<bits/stdc++.h>
using namespace std;
#define re register int
#define LL long long
#define int long long int LCM[3000], total;
int num[20];
int dp[20][3000][50]; int gcd(int x, int y)
{
if(y == 0)return x;
return gcd(y, x%y);
}
int getlcm(int x, int y){return x*y/gcd(x, y);}
int DFS(int pos, int v, int lcm, int limit)
{
if(!pos) return v % lcm == 0;
int &ans = dp[pos][v][LCM[lcm]];
if(!limit && ~ans) return ans;
int ret = 0, up = limit ? num[pos] : 9;
for(re i=0;i<=up;++i)
ret += DFS(pos-1, (v*10+i)%2520, i>0?getlcm(lcm, i):lcm, limit && i == up);
return limit ? ret : ans = ret;
}
LL solve(LL x)
{
int len = 0;
while(x) num[++len] = x % 10, x /= 10;
return DFS(len, 0, 1, 1);
}
signed main()
{
for(re i=1;i<=2520;++i)
if(2520 % i == 0)
LCM[i] = ++total; memset(dp, -1, sizeof dp);
int T;
scanf("%lld",&T);
while(T--)
{
LL x, y;
scanf("%lld%lld",&x,&y);
printf("%lld\n", solve(y) - solve(x-1));
}
}
例题五:恨7不成妻
如果一个整数符合下面3个条件之一,那么我们就说这个整数和7有关——
1、整数中某一位是7;
2、整数的每一位加起来的和是7的整数倍;
3、这个整数是7的整数倍;
现在问题来了:吉哥想知道在一定区间内和7无关的数字的平方和。
数位dp & 热身训练7的更多相关文章
- 数位dp 的简单入门
时间紧张,就不讲那么详细了. 之前一直被深搜代码误解,以为数位dp 其实就是记忆化深搜...(虽说爆搜确实很舒服而且还好想) 但是后来发现数位dp 的标准格式其实是 预处理 + dp ...... 数 ...
- 【学习笔记&训练记录】数位DP
数位DP,即对数位进行拆分,利用数位来转移的一种DP,一般采用记忆化搜索,或者是先预处理再进行转移 一个比较大略的思想就是可以对于给定的大数,进行按数位进行固定来转移记录答案 区间类型的,可以考虑前缀 ...
- 2018牛客网暑假ACM多校训练赛(第四场)C Chiaki Sequence Reloaded (组合+计数) 或 数位dp
原文链接https://www.cnblogs.com/zhouzhendong/p/NowCoder-2018-Summer-Round4-C.html 题目传送门 - https://www.no ...
- 牛客训练三:处女座和小姐姐(三)(数位dp)
题目链接:传送门 思路:数位dp的记忆化搜索模板 从高位向低位枚举,逐位确定每一位的6的个数,dp[i][s]表示处理到第i条边,状态为s时的数字的个数. 注意,要使用long long类型. #in ...
- 专题训练之数位DP
推荐以下一篇博客:https://blog.csdn.net/wust_zzwh/article/details/52100392 1.(HDOJ2089)http://acm.hdu.edu.cn/ ...
- 2019年9月训练(壹)数位DP (HDU 2089)
开学之后完全没时间写博客.... HDU 2089 不要62(vjudge) 数位DP 思路: 题目给出区间[n,m] ,找出不含4或62的数的个数 用一个简单的差分:先求0~m+1的个数,再减去0~ ...
- bzoj3209 花神的数论题 (二进制数位dp)
二进制数位dp,就是把原本的数字转化成二进制而以,原来是10进制,现在是二进制来做,没有想像的那么难 不知到自己怎么相出来的...感觉,如果没有一个明确的思路,就算做出来了,也并不能锻炼自己的能力,因 ...
- dp专题训练
****************************************************************************************** 动态规划 专题训练 ...
- 【BZOJ1662】[Usaco2006 Nov]Round Numbers 圆环数 数位DP
[BZOJ1662][Usaco2006 Nov]Round Numbers 圆环数 Description 正如你所知,奶牛们没有手指以至于不能玩"石头剪刀布"来任意地决定例如谁 ...
随机推荐
- TreeView和ListView数据库查询数据联动操作
好久不用了,重新整理下放这里以备需要使用,功能见图 数据库表结构 定义TreeView addObject中data存储的记录集 type PNode = ^TNode; TNode = record ...
- 使用Python来临时启动端口,用来做安全时候的扫描用
root用户:mkdir /home/aicccd /home/aicc/nohup python -m SimpleHTTPServer 8060 &netstat -antp|grep 8 ...
- 《挑战程序设计竞赛》——DFS
DFS(深度优先搜索) 简介 深度优先搜索(DFS,Depth-First Search)是搜索的手段之一.它从某个状态开始,不断的转移状态直到无法转移.然后退回到前一步的状态,继续转移到其他状态,如 ...
- Django学习day02随堂笔记
每日测验 """ 今日考题 1.谈谈你对web框架的认识,简述web框架请求流程 2.python三大主流web框架的区别 3.安装django需要注意的事项有哪些(最少 ...
- C# 将PPT转为OFD/DPT/DPS/ODP/POTX/UOP
本文分享在C#代码程序中,如何将PPT幻灯片文档转换为多种文件格式,如:OFD.DPT.DPS.ODP.POTX.UOP等.只需在加载PPT幻灯片源文档后,调用ppt.SaveToFile(strin ...
- 开源ASR服务器vosk
概述 近几年由于AI的迅速发展,语音相关的自然语言处理NLP项目也变多了,新的技术也越来越成熟,其中TTS(语音生成)和ASR(语音识别)是NLP中非常重要的环节. 今天我们介绍一个开源的ASR项目v ...
- Redis限流
在电商开发过程中,我们很多地方需要做限流,有的是从Nginx上面做限流,有的是从代码层面限流等,这里我们就是从代码层面用Redis计数器做限流,这里我们用C#语言来编写,且用特性(过滤器,拦截器)的形 ...
- css布局宽度自适应
随着各种终端的不断涌现,网页中的元素适应不同的分辨率变得特别重要,根据经验,涉及到宽度自适应的一共有四种情况: 左端固定,右边自适应:右端固定,左边自适应:两端固定,中间自适应:中间固定,两端自适应. ...
- 定要过python二级 第10套
第一部分 第一题 1. int* 字符串 =几个东西 2. 此题的最开始的疑惑 (1)01 02 03 怎么产生 for 循环 (2)<<< 这个怎么产生 (3)<这个&l ...
- 鸿蒙内核源码分析(内存主奴篇) | 皇上和奴才如何相处 | 百篇博客分析OpenHarmony源码 | v10.04
百篇博客系列篇.本篇为: v10.xx 鸿蒙内核源码分析(内存主奴篇) | 皇上和奴才如何相处 | 51.c.h .o 前因后果相关篇为: v08.xx 鸿蒙内核源码分析(总目录) | 百万汉字注解 ...