数位dp——奏响数字数位的美妙乐章
数位dp:处理数字数位关系的一种dp方式。
一般的题目特征十分明显:
1.一般和数字本身有很大关系。
2.一般求数字在区间L,R中的一些信息
3.L,R一般很大,通常能达到long long级别。
dp方式也比较有套路:
一般有三种方法:
本质上的相似之处,都是集中在处理“填数有无限制”,“填数无限制情况下的固定方案数”,“某些已经搜出来的固定方案数”
1.记忆化搜索(没用过)
是一种倒着记忆的方法。
2.递推(我基本都是这个方法)
正着递推出答案。
一般会从高位向低位递推,讨论高位的填数方法,从而递推出低位的限制与否。
从高位到低位可以通过填的位数处理限制问题。
用一个[0/1]表示,填完这一位之后,是否对后面的数有限制。
填一个数,从k<num,k==num,k>num 讨论。
注意处理可能出现的前导零的锅。
3.预处理一些固定没有限制的一些位置,然后从高位开始填,
没有限制的话,直接查表。
有限制的话,继续填下一位。
注意处理已经填好的位置的固定影响。
经典入门例题:
[ZJOI2010]数字计数
给定两个正整数a和b,求在[a,b]中的所有整数中,每个数码(digit)各出现了多少次。
直接做就好了。
可以处理1~9,1~99,。。的固定答案,或者直接递推下去。
处理1~9,1~99,1~999...的方法:
Code:
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N=;
ll a,b;
ll cnta[],cntb[];
ll f[],ten[];
void query(ll x,ll *cnt)
{
int len=;
int num[];
ll k=x;
while(k)
{
num[++len]=k%;
k/=;
}
for(int i=len;i>=;i--)
{
for(int j=;j<=;j++)
cnt[j]=cnt[j]+f[i-]*num[i];
for(int j=;j<num[i];j++)
cnt[j]+=ten[i-];
ll num2=;
for(int j=i-;j>=;j--)
{
num2=num2*+num[j];
}
num2++;
cnt[num[i]]+=num2;
cnt[]-=ten[i-];
}
}
int main()
{
scanf("%lld%lld",&a,&b);
ten[]=;
for(int i=;i<=;i++)
{
f[i]=f[i-]*+ten[i-];
ten[i]=ten[i-]*;
}
query(a-,cnta);
query(b,cntb);
for(int i=;i<=;i++)
printf("%lld ",cntb[i]-cnta[i]);
return ;
}
数字计数1
递推处理:
Code:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=;
struct node{
ll a[];
void init(){
memset(a,,sizeof a);
}
node operator +(const node &b){
node c;c.init();
for(int i=;i<=;i++)
c.a[i]=a[i]+b.a[i];
return c;
}
node operator *(const int &x){
node c;c.init();
for(int i=;i<=;i++)
c.a[i]=a[i]*x;
return c;
}
node operator -(const node &b){
node c;c.init();
for(int i=;i<=;i++)
c.a[i]=a[i]-b.a[i];
return c;
}
void op(){
cout<<endl;
for(int i=;i<=;i++)
cout<<" "<<a[i];cout<<endl;cout<<endl;
}
}f[N][],ans,kk;
int num[N],cnt;
ll ten[N];
ll pre[N];
ll A,B;
void sol(){
for(int i=;i<=cnt+;i++)
for(int j=;j<=;j++)
f[i][j].init();
for(int i=cnt;i>=;i--){
//cout<<num[i]<<" dig"<<endl;
f[i][]=f[i+][];f[i][].a[num[i]]++;
f[i][]=(f[i+][]*num[i])+(f[i+][]*);
for(int j=;j<=;j++)
{
if(j<num[i]) f[i][].a[j]+=(pre[i+]+);
else f[i][].a[j]+=pre[i+];
}
//f[i][1].op();
//f[i][0].op();
}
for(int i=cnt;i>=;i--){
f[][].a[]-=ten[i-];
}
}
int main()
{
scanf("%lld%lld",&A,&B);ten[]=;
for(int i=;i<=;i++)ten[i]=ten[i-]*(ll);
while(B){
num[++cnt]=B%;
pre[cnt]=B;B/=;
}
//pre[cnt+1]=1;
sol();
ans=ans+f[][]+f[][]; cnt=;memset(num,,sizeof num);
memset(pre,,sizeof pre);
A--;
while(A){
num[++cnt]=A%;
pre[cnt]=A;
A/=;
}
//pre[cnt+1]=1; sol();
kk=kk+f[][]+f[][];
ans=ans-kk; for(int i=;i<=;i++){
printf("%lld ",ans.a[i]);
}
return ;
}
数字计数2
[SCOI2009]windy数
windy定义了一种windy数。不含前导零且相邻两个数字之差至少为2的正整数被称为windy数。 windy想知道,
在A和B之间,包括A和B,总共有多少个windy数?
Solution:
直接记录最后一个数是什么,要记录是否已经出现了非零数字,即前导零。处理前导零bug
因为前导零不处理,可能会认为最后一位填的是0,从而不能填1、0了。
f[i][last][0/1限制][0/1有无出现非零数字]
Code:
#include<bits/stdc++.h>
using namespace std;
const int N=;
int f[N][N][][];
int num[N],cnt;
int A,B;
int wrk(){
memset(f,,sizeof f);
for(int i=;i<num[cnt];i++) f[cnt][i][][]=;
f[cnt][num[cnt]][][]=;
f[cnt][][][]=; for(int i=cnt;i>=;i--){
for(int j=;j<=;j++){
for(int k=;k<=;k++){
if(abs(k-j)>=){
if(j==num[i]&&k==num[i-]) f[i-][k][][]+=f[i][j][][];
if(j==num[i]&&k<num[i-]) f[i-][k][][]+=f[i][j][][];
f[i-][k][][]+=f[i][j][][];
if(j==) f[i-][k][][]+=f[i][j][][];
}
}
}
f[i-][][][]+=f[i][][][];
f[i-][][][]+=f[i][][][];
}
int ret=;
for(int j=;j<=;j++){
ret+=f[][j][][]+f[][j][][];
}
//ret+=f[1][0][0][0];
return ret;
}
int main()
{
scanf("%d%d",&A,&B);
while(B){
num[++cnt]=B%;B/=;
}
int ansB=wrk(); A--;cnt=;
while(A){
num[++cnt]=A%;A/=;
}
int ansA=wrk(); //cout<<ansB<<" "<<ansA<<endl; printf("%d",ansB-ansA);
return ;
}
windy数
SAC#1 - 萌数
辣鸡蒟蒻SOL是一个傻逼,他居然觉得数很萌!
好在在他眼里,并不是所有数都是萌的。只有满足“存在长度至少为2的回文子串”的数是萌的——也就是说,101是萌的,因为101本身就是一个回文数;110是萌的,因为包含回文子串11;但是102不是萌的,1201也不是萌的。
现在SOL想知道从l到r的所有整数中有多少个萌数。
由于答案可能很大,所以只需要输出答案对1000000007(10^9+7)的余数。
Solution:
抓住问题本质,长度至少为2的回文子串,只要有2或者3就一定满足。
所以,只要记录上一位,上上位填的数即可。
还要记录是否已经出现过长度为2或者3的回文串。
同样,前导零的锅也要处理的。
Code:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=+;
const int mod=1e9+;
int n,m;
char a[N],b[N];
int la,lb;
ll ans;
ll r,l;
ll f[N][][][][][];// lim ; has huiwen ; has >=1
ll wrk(char *s,int len){
memset(f,,sizeof f);
for(int j=;j<=s[]-'';j++){
if(j!=){
if(j<s[]-'')f[][][][][j][]=;
else f[][][][][j][]=;
}
else f[][][][][][]=;
}
if(len==) return ;
for(int i=;i<=lb;i++){
for(int j=;j<=;j++){
for(int k=;k<=;k++){
for(int p=;p<=;p++){
if(p<s[i]-''){ if(p==k||p==j) (f[i][][][k][p][]+=f[i-][][][j][k][]+f[i-][][][j][k][])%=mod;
else (f[i][][][k][p][]+=f[i-][][][j][k][]+f[i-][][][j][k][])%=mod;
(f[i][][][k][p][]+=f[i-][][][j][k][]+f[i-][][][j][k][])%=mod;
}
else if(p==s[i]-''){
if(p==k||p==j) (f[i][][][k][p][]+=f[i-][][][j][k][])%=mod;
else (f[i][][][k][p][]+=f[i-][][][j][k][])%=mod;
(f[i][][][k][p][]+=f[i-][][][j][k][])%=mod; if(p==k||p==j) (f[i][][][k][p][]+=f[i-][][][j][k][])%=mod;
else (f[i][][][k][p][]+=f[i-][][][j][k][])%=mod;
(f[i][][][k][p][]+=f[i-][][][j][k][])%=mod;
}
else{
if(p==k||p==j) (f[i][][][k][p][]+=f[i-][][][j][k][])%=mod;
else (f[i][][][k][p][]+=f[i-][][][j][k][])%=mod;
(f[i][][][k][p][]+=f[i-][][][j][k][])%=mod;
}
}
}
}
for(int p=;p<=;p++){
if(p==) (f[i][][][][p][]+=f[i-][][][][][])%=mod;
else (f[i][][][][p][]+=f[i-][][][][][])%=mod;
}
}
ll ret=;
for(int j=;j<=;j++)
for(int k=;k<=;k++){
(ret+=f[len][][][j][k][]+f[len][][][j][k][])%=mod;
}
return ret;
}
int main()
{
scanf("%s",a+);scanf("%s",b+);
la=strlen(a+),lb=strlen(b+); l=wrk(a,la);
r=wrk(b,lb); bool fl=false;
for(int i=;i<=la;i++){
if(a[i]==a[i-]||a[i]==a[i-]) fl=true;
}
ans=(r+fl-l+mod)%mod;
printf("%lld",ans);
return ;
}
萌数
bzoj 3329 Xorequ
Description
Input
第一行一个正整数,表示数据组数据 ,接下来T行
每行一个正整数N
Output
2*T行
第2*i-1行表示第i个数据中问题一的解,
第2*i行表示第i个数据中问题二的解,
HINT
x=1与x=2都是原方程的根,注意第一个问题的解
不要mod 10^9+7
1<=N<=10^18
1<=T<=1000
Solution:
首先一定要化简x^3x=2x
即:3x=x^2x ; x+2x=x^2x 说明,2倍的x和x没有公共1位置,否则就会变小了。
2x就是x二进制下左移一位,所以,x的条件其实是,x的二进制表示下没有连续的两个1.
对于第一问,记录上一位填的是0/1即可转移。
对于第二问,发现,是2的整次幂。2^n显然满足条件,
这里假设可以是0,不需要正整数条件,而0显然也满足条件。
最后也不用减,因为2^n本来就要加1个。
即处理有n位可以填0/1。
设f[i]表示,i位随便填0/1,符合的数的个数。
第i位填1的话,i-1位只能填零,方案数就是f[i-2]
第i位填0的话,对i-1位没有影响,就是f[i-1]
所以f[i]=f[i-1]+f[i-2],f[1]=2,f[2]=3
一个斐波那契数列。
矩阵乘法优化一下即可。
Code:(多组数据记得memset)
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=;
const int mod=1e9+;
ll f[N][][];
int num[N],cnt;
ll wrk1(){
memset(f,,sizeof f);
f[cnt+][][]=;
for(int i=cnt;i>=;i--){
for(int k=;k<=;k++){
if(k<num[i]){
if(k==) f[i][][k]+=f[i+][][]+f[i+][][];
else f[i][][k]+=f[i+][][]+f[i+][][]+f[i+][][]+f[i+][][];
}
else if(k==num[i]){
if(k==) f[i][][k]+=f[i+][][],f[i][][k]+=f[i+][][];
else f[i][][k]+=f[i+][][]+f[i+][][],f[i][][k]+=f[i+][][]+f[i+][][];
}
else {
if(k==) f[i][][k]+=f[i+][][];
else f[i][][k]+=f[i+][][]+f[i+][][];
}
}
}
ll ret=;
ret=f[][][]+f[][][]+f[][][]+f[][][];
return ret-;
}
struct tr{
ll a[][];
void pre(){
memset(a,,sizeof a);
}
void init(){
for(int i=;i<=;i++) a[i][i]=;
}
tr operator *(const tr &b){
tr c;c.pre();
for(int i=;i<=;i++)
for(int k=;k<=;k++)
for(int j=;j<=;j++){
(c.a[i][j]+=a[i][k]*b.a[k][j]%mod)%=mod;
}
return c;
}
}A,S,B;
tr qm(tr x,ll y){
tr ret;ret.pre();ret.init();
while(y){
if(y&) ret=ret*x;
x=x*x;
y>>=;
}
return ret;
}
int t;
ll n;
int main()
{
scanf("%d",&t);
A.a[][]=,A.a[][]=;
B.a[][]=,B.a[][]=;
B.a[][]=,B.a[][]=; while(t--){
scanf("%lld",&n);
ll nn=n;
cnt=;
while(nn){
num[++cnt]=nn%;nn/=;
}
ll ans1=wrk1();
printf("%lld\n",ans1); ll ans2;
if(n==){
ans2=;
}
else if(n==){
ans2=;
}
else{
S=A*qm(B,n-);
ans2=S.a[][]%mod;
}
printf("%lld\n",ans2);
}
return ;
}
Xorequ
CF55D Beautiful numbers
Volodya是一个很皮的男孩。他认为一个能被它自己的每一位数上的数整除(非0)的数是很妙的。我们先忽略他的想法的正确性,只回答在l到r之间有多少个很妙的数字。
输入输出格式
输入:总共有t个询问:
第一行:t;
接下来t行:每行两个数l和r。
注意:请勿使用%lld读写长整型(虽然我也不知道为什么),请优先使用cin(或者是%I64d)。
输出:t行,每行为一个询问的答案。
Solution:
比较巧妙的状态设计,还有优化 的题目。
直接记录被1,2,3..除是肯定不行的,。
发现,lcm(1,2.。。9)=2520
所以,如果最后能被1,2.。。9整除,那么把这个数对2520取模,不会影响整除与否的。
所以,f[i][2520][S]表示,填到i位,对2520取模结果,出现数字的状压集合。
但是,这个题有10组数据,就T飞了。
考虑优化没用的状态。
S大小是256的,不用记0,不用记1,要记8个。
但是,最后要找的还是S出现数字的lcm能否整除x
所以,就直接记录lcm就行了。
2250=2^3*3^2*5*7,一共只有48种lcm,离散化记录,256->48
但是还是过不去(可能我的代码常数太大?)
发现,记录mod 2250的余数时,假设之前是j,
那么,新的=j*10+k mod 2250(k是当前位的数)
设x=a2250+j;
每次j要乘10,是不是记录mod 225即可?
x=a'225 + j'
x'=a'2250+10j'+k
发现,x mod 2250 的只和j‘有关,
所以,记录j'即可,每次mod 225记录新的j
最后一次,mod 2250 就是mod 2250 的余数了。
Code:(luogu最优解)
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=;
const int up=;
ll L,R;
int a[N],cnt;
ll f[N][][][];
ll g[][][];
int lcm[],tot;
int id[];
int to[][];
int gcd(int p,int q){
return q?gcd(q,p%q):p;
}
void dfs(int x,int lc){
//cout<<" x lc "<<x<<" "<<lc<<endl;
if(x==){
lcm[++tot]=lc;
return;
}
dfs(x+,lc);
dfs(x+,lc*x/gcd(x,lc));
}
ll wrk(){
ll ret=;
memset(f,,sizeof f);
memset(g,,sizeof g);
f[cnt+][][][]=;
for(int i=cnt;i>=;i--){
for(int j=;j<;j++){
for(int k=;k<=tot;k++){
for(int p=;p<=;p++){
if(i!=){
if(p<a[i]){
f[i][(j*+p)%up][][to[k][p]]+=f[i+][j][][k]+f[i+][j][][k]; }
else if(p==a[i]){
f[i][(j*+p)%up][][to[k][p]]+=f[i+][j][][k];
f[i][(j*+p)%up][][to[k][p]]+=f[i+][j][][k];
}
else {
f[i][(j*+p)%up][][to[k][p]]+=f[i+][j][][k];
}
}
else{
if(p<a[i]){
g[(j*+p)%][][to[k][p]]+=f[i+][j][][k]+f[i+][j][][k]; }
else if(p==a[i]){
g[(j*+p)%][][to[k][p]]+=f[i+][j][][k];
g[(j*+p)%][][to[k][p]]+=f[i+][j][][k];
}
else {
g[(j*+p)%][][to[k][p]]+=f[i+][j][][k];
}
}
}
}
}
}
for(int j=;j<;j++){
for(int k=;k<=tot;k++){
if(j%lcm[k]==){
ret+=g[j][][k]+g[j][][k];
}
}
} return ret;
}
int T;
ll ansl,ansr;
int main()
{
dfs(,);
sort(lcm+,lcm+tot+);
//cout<<tot<<endl;
tot=unique(lcm+,lcm+tot+)-lcm-;
for(int i=;i<=tot;i++) id[lcm[i]]=i;
for(int i=;i<=tot;i++){
to[i][]=i;
for(int j=;j<=;j++){
to[i][j]=id[lcm[i]*j/gcd(lcm[i],j)];
}
} /*cout<<" tot "<<tot<<endl;
for(int i=1;i<=tot;i++){
cout<<lcm[i]<<" "<<id[lcm[i]]<<" "<<endl;
for(int j=0;j<=9;j++){
cout<<" with "<<j<<" : "<<to[i][j]<<endl;
}
}*/ scanf("%d",&T);
while(T--){
scanf("%I64d%I64d",&L,&R);
ansl=ansr=;
L--; if(L==) ansl=;
else{cnt=;
while(L){
a[++cnt]=L%;L/=;
}ansl=wrk();
} cnt=;
while(R){
a[++cnt]=R%;R/=;
}ansr=wrk(); printf("%I64d\n",ansr-ansl);
}
return ;
}
Beautiful numbers
[CQOI2016]手机号码
比较麻烦的数位dp
见另一片博客:
[SCOI2014]方伯伯的商场之旅
非常麻烦的数位dp了。
见另外一篇博客:
数位dp——奏响数字数位的美妙乐章的更多相关文章
- 【数位DP】数字统计
题目 给定两个正整数a和b,求在[a,b]中的所有整数中,每个数码(digit)各出现了多少次. 数位DP (1)分情况,逐位讨论. (2)模型:计算在[L,R]中有多少个数满足条件. (3)套路:将 ...
- 洛谷 - P2602 - 数字计数 - 数位dp
https://www.luogu.org/problemnew/show/P2602 第二道数位dp,因为“数位dp都是模板题”(误),所以是从第一道的基础上面改的. 核心思想就是分类讨论,分不同情 ...
- 【学习笔记&训练记录】数位DP
数位DP,即对数位进行拆分,利用数位来转移的一种DP,一般采用记忆化搜索,或者是先预处理再进行转移 一个比较大略的思想就是可以对于给定的大数,进行按数位进行固定来转移记录答案 区间类型的,可以考虑前缀 ...
- Codeforces Round #235 (Div. 2) D. Roman and Numbers (数位dp、状态压缩)
D. Roman and Numbers time limit per test 4 seconds memory limit per test 512 megabytes input standar ...
- 数位dp 的简单入门
时间紧张,就不讲那么详细了. 之前一直被深搜代码误解,以为数位dp 其实就是记忆化深搜...(虽说爆搜确实很舒服而且还好想) 但是后来发现数位dp 的标准格式其实是 预处理 + dp ...... 数 ...
- hdu6148 百度之星程序设计竞赛复赛 (数位dp)
Valley Numer Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others)Tota ...
- 递推、数位DP解析(以HDU 2089 和 HDU 3555 为例)
HDU 2089 不要62 题目链接 http://acm.hdu.edu.cn/showproblem.php?pid=2089 Problem Description 杭州人称那些傻乎乎粘嗒嗒的人 ...
- 【hdu4507】吉哥系列故事——恨7不成妻 数位dp
题目描述 求 $[L,R]$ 内满足:数位中不包含7.数位之和不是7的倍数.本身不是7的倍数 的所有数的平方和 mod $10^9+7$ . 输入 输入数据的第一行是case数T(1 <= T ...
- [hdu 2089] 不要62 数位dp|dfs 入门
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=2089 题意:求[n, m]区间内不含4和62的数字个数. 这题有两种思路,直接数位dp和dfs 数位d ...
随机推荐
- 20155334 曹翔 Exp2 后门原理与实践
20155334 曹翔 Exp2 后门原理与实践 不多废话直接上实验过程,本实验的所有端口都是5334. 一.实验过程 查询主机Windows和虚拟机kali的ip地址: Windows获得Linux ...
- 总结:C# 委托的全面理解
在说事件之前得先了解委托. 委托,外表看来和C/C++中函数指针没什么区别,但是本质上你才发现他其实就是个类!也就是说理解委托得从 这个两个方面去理解(单从一个方面去理解感觉就怪怪的呵呵!) 理解委托 ...
- STM32一键下载电路设计原理
先放原理图(补充:图中的BOOT0通过10K的电阻接到地),再解释为什么这么设计: STM32启动方式:BOOT0和 BOOT1用于设置 STM32的启动方式 ,见下表: BOOT0=1,BOOT1= ...
- python3获取文件及文件夹大小
获取文件大小 os.path.getsize(file_path):file_path为文件路径 >>> import os >>> os.path.getsize ...
- ElasticSearch入门 第三篇:索引
这是ElasticSearch 2.4 版本系列的第三篇: ElasticSearch入门 第一篇:Windows下安装ElasticSearch ElasticSearch入门 第二篇:集群配置 E ...
- java中多态的实现机制
多态的概念: 简单来说就是事物在运行过程中存在的不同状态,即父类或接口定义的引用变量指向子类或具体实现类的实例对象.程序调用方法在运行期才进行动态绑定,而不是引用变量的类型中定义的方法. 多态存在的前 ...
- PAT甲题题解-1021. Deepest Root (25)-dfs+并查集
dfs求最大层数并查集求连通个数 #include <iostream> #include <cstdio> #include <algorithm> #inclu ...
- 《Linux内核 》MOOC 课程
姬梦馨 原创微博 <Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000 学习笔记 一:什么是冯诺依曼体系结构? ...
- linux第一次实验报告
http://wwwcnblogs.com/20135228guoyao/4964477.html
- c语言四则运算
#include<stdio.h>#define W 5main(){ int a,b,i=0,c,d,r=0; while(i<W) { i++; srand(time()); a ...