数位DP笔记
数位DP
1.定义:
数位dp是一种计数用的dp,一般就是要统计一个区间[L,R]内满足一些条件数的个数。所谓数位dp,字面意思就是在数位上进行dp;
数位的含义:一个数有个位、十位、百位、千位......数的每一位就是数位
2.替代
数位DP 都可以通过打表以及记搜来写,但是我搜索写的不好/kk
3.自己做数位DP的一些教训
- 对于进制拆分的时候边界要注意,看看自己统计答案的时候能不能取到边界
- 对于前导0的处理,根据是否合法进行处理
- 结果用到dp出来的值要统计完全,不能遗漏
4.原理……
这个东西大概就是通过数位的拆分,各个数位上的dp值会满足乘法原理这一类的知识然后将各个数位上的dp值统计得出\([1 ~ L-1]\) 的值同理得到\([1 ~ R]\)的值,然后利用前缀和的思想$$ans_{L,R} = ans_{1,R} - ans_{1,L-1}$$
得出答案数位DP大部分都是这样一个模板所以说它基本不考,我也不知道为啥学它,它还这么难
5. 例题
1.0 有一说一这个题不该评蓝,这个题比下面那个题简单多了
windy数
简化题意:
输入一个\(L\)和\(R\),求\([L,R]\)之间的windy数
windy数:不含前导0的相邻两个数之间差值至少为2
\(\exists x,y \in N^+ ,|x-y| \geqslant 2\) 则x,y为windy数
solution:
- 从总体上看有\(ans_{L,R} = ans_{1,R} - ans_{1,L-1}\)
- 然后处理的就有 \([1,L-1]\) 与 \([1,R]\)
- 考虑数位DP,首先考虑每一位上的数对最终答案的贡献,预处理每一位上的贡献设立状态\(f_{i,j}\)记录第i位上的数位j的贡献$$f_{i,j} += f_{i-1,k} , |j-k| \geqslant 2$$
- 对答案进行统计即计算[1,L-1]的贡献与[1,R]的贡献
len 表示的为 区间右端点的数位长度
a[i]表示第i 位上的数
- 第一部分对位数小于\(len\)的数的贡献直接统计 \(ans += f_{i,j},j \in[1,9]\)
- 第二部分对最高位但是最高位值小于\(a_len\)的贡献直接统计 \(ans += f_{len,i}\), \(i\in\) \([1,a[len])\)
- 第三部分对剩下的\(len - 1\)位 重复进行第二部分的操作只不过最高位变成了第i位
\begin{Bmatrix} \sum\limits_{i = 1}^{i <len}\sum\limits_{j = 1}^{j \le 9} f_{i,j}\\
\sum\limits_{i = 1}^{i < a[len]}f_{len,i}\\
\sum\limits_{i = 1 } ^ {i < len} \sum\limits_{j = 1} ^ { j < a[i]} f_{i,j}
\end{Bmatrix}
\]
写的好丑/kk
预处理部分
void init(){
for(ll i = 0 ; i <= 9 ; i++) f[1][i] = 1;
for(ll i = 2 ; i <= 10 ; i++) {
for (ll j = 0; j <= 9; j++) {
for(ll k = 0 ; k <= 9 ; k++)
if(abs(j - k) >= 2) f[i][j] += f[i-1][k];
// cout<<f[i][j]<<" ";
}
// puts("");
}
}
第一部分
for(ll i = 1 ; i < len ;i++)
for(ll j = 1; j <= 9 ;j++)
ans += f[i][j];
// cout<<ans<<" ";
第二部分
for(ll i = 1 ; i < a[len] ; i++) ans += f[len][i];
// cout<<ans<<" ";
第三部分
for(ll i = len - 1 ; i >= 1 ;i--){
for(ll j = 0 ; j < a[i] ;j++)
if(abs(j - a[i + 1] ) >= 2) ans += f[i][j];
if(abs(a[i + 1 ] - a[i]) < 2 ) break;
}
5.code
#include <iostream>
#include <cstdio>
#include <cmath>
#include <string>
#include <cstring>
#define ll long long
using namespace std;
ll read() {
ll s = 0, f = 0;
char ch = getchar();
while (!isdigit(ch)) f |= ch == '-', ch = getchar();
while (isdigit(ch)) s = s * 10 + (ch ^ 48), ch = getchar();
return f ? -s : s;
}
ll f[20][20],a[20];
void init(){
for(ll i = 0 ; i <= 9 ; i++) f[1][i] = 1;
for(ll i = 2 ; i <= 10 ; i++) {
for (ll j = 0; j <= 9; j++) {
for(ll k = 0 ; k <= 9 ; k++)
if(abs(j - k) >= 2) f[i][j] += f[i-1][k];
// cout<<f[i][j]<<" ";
}
// puts("");
}
}
ll ans , len;
ll solve(ll x) {
memset(a,0,sizeof(a));
ans = 0 ;
len = 0;
while(x){
a[++len] = x % 10;
x /= 10;
}
for(ll i = 1 ; i < len ;i++)
for(ll j = 1; j <= 9 ;j++)
ans += f[i][j];
// cout<<ans<<" ";
//处理比len短的部分
for(ll i = 1 ; i < a[len] ; i++) ans += f[len][i];
//处理第 len 位(最高位)
// cout<<ans<<" ";
for(ll i = len - 1 ; i >= 1 ;i--){
for(ll j = 0 ; j < a[i] ;j++)
if(abs(j - a[i + 1] ) >= 2) ans += f[i][j];
if(abs(a[i + 1 ] - a[i]) < 2 ) break;
}
//处理非len位的
// cout<<ans<<" ";
return ans;
}
int main() {
// freopen("1.in","r",stdin);
// freopen("2657.out","w",stdout);
ll L = read(), R = read();
init();
R++;
cout<<solve(R)-solve(L);
return 0;
}
2.同样的套路题
只不过\(f_{i,j} = \sum f_{i-1,k} , k\in [1,9]\)
\(f_{i,j}\)的含义与上个题一样
转移方程减少了限制但最后的求和满足乘法原理,而且注意取模,因为取模只后相对大小会改变,所以要
答案变成了
\begin{Bmatrix} \sum\limits_{ i = 1,j = 1}^{i <len,j \leqslant 9} f_{i,j}\\
\sum\limits_{i = 1}^{i < a[len]}f_{len,i}\\
\sum\limits_{i = 1 } ^ {i < len} \times \sum_{j = 1} ^ { j < a[i]} f_{i,j}
\end{Bmatrix}
\]
满足乘法原理,而且注意快速幂取模,LATEX用的不好大概没写错
code
#include <iostream>
#include <cstdio>
#include <string>
#include <cstring>
#define int long long
using namespace std;
const int mod = 1e9 + 7;
inline int read() {
int s = 0, f = 0;
char ch = getchar();
while (!isdigit(ch)) f |= ch == '-', ch = getchar();
while (isdigit(ch)) s = s * 10 + (ch ^ 48), ch = getchar();
return f ? -s : s;
}
int L, R, T, len, ans;
int f[20][20],a[20];
int f_pow(int x ,int y){
int ans = 1 ;
while(y) {
if(y & 1) ans = (ans * x) %mod;
x = (x * x) % mod;
y >>= 1;
}
return ans % mod;
}
void init(){
memset(f , 0, sizeof(f));
for(int i = 0 ;i <= 9 ;i++) f[1][i] = i;
for(int i = 2 ; i <= 18; i++) {
for(int j = 0; j <= 9 ;j++) {
for(int k = 0 ; k <= 9 ;k++)
f[i][j] = (f[i][j] + f[i - 1][k]) % mod;
f[i][j] = (f[i][j] + j * f_pow(10, i - 1)) % mod;
// cout<<"i: "<<i<<" j:"<<j<<" "<< f[i][j] << " ";
}
// cout<<"\n";
}
}
int solve(int x ){
memset(a, 0, sizeof(a));
len = ans= 0;
int sum = 0;
while(x){
a[++len] = x % 10;
x /= 10;
}
for(int i = 1 ; i < len ;i++)
for(int j = 1 ; j <= 9;j++)
ans = (ans + f[i][j]) % mod;
//处理比它短的 全都加进去
for(int i = 1 ; i < a[len] ; i++) ans = (ans + f[len][i]) % mod;
//处理跟它一样长但是比它第len位小的
// cout<<ans<<"\n";
sum += a[len];
//把第len位的贡献加进去
for(int i = len - 1 ; i >= 1 ;i-- ){
for(int j = 0 ; j < a[i] ;j++)
ans = (ans + f[i][j]) % mod;
ans = (ans + sum * a[i] * f_pow(10 ,i - 1) % mod)% mod;
sum += a[i];
}
//处理跟它一样长但是小于第 i 位上
return ans % mod;
}
signed main() {
T = read();
init();
while (T--) {
L = read(), R = read();
printf("%lld\n",(solve(R + 1) -solve(L) + mod) %mod);
}
return 0;
}
3.小小的变形
这个题小小的不一样,设定状态\(f[i][j][k]\)表示第\(i\) 位上的数(二进制拆分后) 为 \(j\),\(1\)的个数为 \(k\)
可得转移方程
\(
f_{i,1,k} = \sum\limits_{p = 0}^{p \ge 1} \sum_{k = 0}^{k \ge i} f_{i-1,p,k-1}
\)
\(
f_{i,0,k} = \sum\limits_{p = 0}^{p \ge 1} \sum_{k = 0}^{k \ge i} f_{i-
1,p,k}
\)
求解最终答案时,统计一下0的个数和1的个数,分别记录为\(cnt1\)和\(cnt0\)得到最终答案:
\begin{Bmatrix} \ f_{i,0,j}, i\in[1,len),j \in[0,len/2-cnt1] \\
f_{i,1,j},i \in[1,len),j \in[0,i/2]
\end{Bmatrix}
\]
本来想放大一点的,但式子貌似有点长
Code
#include <iostream>
#include <cstdio>
#include <cstring>
#include <string>
#define int long long
using namespace std;
int f[40][11][40];
int a[40];
int read() {
int s = 0, f = 0;
char ch = getchar();
while (!isdigit(ch)) f |= ch == '-', ch = getchar();
while (isdigit(ch)) s = s * 10 + (ch ^ 48), ch = getchar();
return f ? -s : s;
}
void init(){
f[1][1][1] = 1 , f[1][0][0] = 1;
for(int i = 2 ; i < 33 ;i++)
for(int j = 0 ;j <= 1 ;j++ )
for(int k = 0 ; k <= i ;k++)
for(int p = 0 ; p <= 1 ;p++)
if(!j) f[i][j][k] += f[i-1][p][k];
else if(k) f[i][j][k] += f[i-1][p][k - 1];
}
int solve(int x){
memset(a,0,sizeof(a));
int len = 0;
while(x) {
a[++len] = x % 2;
x /= 2;
}
int ans = 0,cnt1= 1 , cnt0 = 0;
for(int i = len - 1; i >= 1 ;i--) {
int x = a[i];
if(x) {
for(int j = 0 ; j <= len / 2 - cnt1 ;j++) {
ans += f[i][0][j];
}
}
cnt1 += x;
cnt0 += (x == 0);
if(cnt0 >= cnt1 && i == 1) ans++;
}
for(int i = 1 ; i < len ;i++)
for(int j = 0 ; j <= i/2 ;j++)
ans += f[i][1][j];
return ans;
}
signed main(){
init();
int L = read() , R = read();
cout<<solve(R) - solve(L-1);
return 0;
}
感觉自己数位DP还是没学好
数位DP笔记的更多相关文章
- 数位dp 笔记
目录 数位dp 笔记 解决的问题 & 主体思想 入门 -- windy数 绕一个弯 -- 萌数 the end? -- 恨7不成妻 小心细节 [SDOI2016]储能表 复杂度起飞 [AHOI ...
- 「笔记」数位DP
目录 写在前面 引入 求解 特判优化 代码 例题 「ZJOI2010」数字计数 「AHOI2009」同类分布 套路题们 「SDOI2014」数数 写在最后 写在前面 19 年前听 zlq 讲课的时候学 ...
- 数位DP学习笔记
数位DP学习笔记 什么是数位DP? 数位DP比较经典的题目是在数字Li和Ri之间求有多少个满足X性质的数,显然对于所有的题目都可以这样得到一些暴力的分数 我们称之为朴素算法: for(int i=l_ ...
- 算法笔记--数位dp
算法笔记 这个博客写的不错:http://blog.csdn.net/wust_zzwh/article/details/52100392 数位dp的精髓是不同情况下sta变量的设置. 模板: ]; ...
- 数位DP复习笔记
前言 复习笔记第五篇.(由于某些原因(见下),放到了第六篇后面更新)CSP-S RP++. luogu 的难度评级完全不对,所以换了顺序,换了别的题目.有点乱,见谅.要骂就骂洛谷吧,原因在T2处 由于 ...
- 【学习笔记&训练记录】数位DP
数位DP,即对数位进行拆分,利用数位来转移的一种DP,一般采用记忆化搜索,或者是先预处理再进行转移 一个比较大略的思想就是可以对于给定的大数,进行按数位进行固定来转移记录答案 区间类型的,可以考虑前缀 ...
- bzoj 1026: [SCOI2009]windy数 & 数位DP算法笔记
数位DP入门题之一 也是我所做的第一道数位DP题目 (其实很久以前就遇到过 感觉实现太难没写) 数位DP题目貌似多半是问从L到R内有多少个数满足某些限制条件 只要出题人不刻意去卡多一个$log$什么的 ...
- 数位DP 学习笔记
前言:鸣谢https://www.luogu.com.cn/blog/virus2017/shuweidp.感谢大佬orz ----------------------------- [引入] 首先要 ...
- [学习笔记] 数位DP的dfs写法
跟着洛谷日报走,算法习题全都有! 嗯,没错,这次我也是看了洛谷日报的第84期才学会这种算法的,也感谢Mathison大佬,素不相识,却写了一长篇文章来帮助我学习这个算法. 算法思路: 感觉dfs版的数 ...
随机推荐
- 使用 CoreDNS 来应对 DNS 污染
原文链接:https://fuckcloudnative.io/posts/install-coredns-on-macos/ CoreDNS 是 Golang 编写的一个插件式 DNS 服务器,是 ...
- Linux嵌入式学习-烟雾传感器驱动-字符设备驱动-按键驱动
MQ-2烟雾气敏传感器模块在X210v3开发板上的驱动. 现在需要一个MQ-2烟雾气敏传感器模块的驱动.其检测烟雾超过一定的标准后,会返回一个不同的电平,和按键驱动差不多. 但是在编写驱动的时候,需要 ...
- 初探JAVA内部类细节一
定义: 可以将一个类的定义放在另一个类的内部 这就是内部类.--摘自java编程思想 一般实现方式: public class SimpleInnerClass { class Content { p ...
- v-on以及v-show、v-if的一些小杂碎
v-on的参数问题: 当通过methods中定义方法,以供@click调用时,需要注意参数问题: 情况一:如果该方法不需要额外参数,那么方法后的()可以不添加.但是注意:如果方法本身中有一个参数,那么 ...
- 2.2.1 Sqoop1的基本架构
当用户通过shell命令提交迁移作业后,Sqoop会从关系型数据库中读取元信息,并根据并发度和数据表大小将数据划分成若干分片,每片交给一个Map Task处理,这样多个Map Task同时读取数据库中 ...
- 感谢 Gridea,让我有动力写作
1. 真的要感谢 Gridea,让我对写作产生热忱.一直有在各大博客平台输出的习惯,但是都没有持续更新.有的平台广告太多,写不下去.有的平台排版复杂,写文章1个小时,排版要2个小时.所以后面换成了静态 ...
- 探索 .NET团队对API的设计流程
在这篇文章中,我想介绍一些我觉得非常有趣的东西,.NET 团队是如何设计API的? 我们先来看下.NET团队面临的有哪些挑战,您正在设计一套API库,每天有数百万的开发人员在使用这些库,它们在世界各地 ...
- 观《if (domain logic) then CQRS, or Saga?》所悟
引言 Udi Dahan曾在2017年阿姆斯特丹的DDD欧洲年会上发表过一篇演讲--if (domain logic) then CQRS, or Saga.视频是UP主从Youtube搬运的,我听力 ...
- 【基础】1001_Hello,World!
题目相关 [题目描述] 编写一个能够输出"Hello,World!"的程序,这个程序常常作为一个初学者接触一门新的编程语言所写的第一个程序,也经常用来测试开发.编译环境是否能够正常 ...
- openstack octavia的实现与分析(二)原理,架构与基本流程
[了解] 其实说白了,Octavia就是将用户的API请求经过逻辑处理,转换成Haproxy或者Nginx的配置参数,下发到amphora虚机中. Octavia的内部实现中,逻辑流程的处理主要使用T ...