数位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 ...
随机推荐
- Wild Dog sample [sync data]
<html> <head> <meta charset="UTF-8"> <title>test wilddog </titl ...
- 20155217《网络对抗》Exp03 免杀原理与实践
20155217<网络对抗>Exp03 免杀原理与实践 实践内容 正确使用msf编码器,msfvenom生成如jar之类的其他文件,veil-evasion,自己利用shellcode编程 ...
- 利用git将项目上传到github
本文主要介绍如果用git将项目上传到githup. 一.准备工作 (1)欲将项目上传到githup,先在githup上新建一个仓库.这里就不介绍. (2 ...
- effective c++ 笔记 (35-40)
//---------------------------15/04/24---------------------------- //#35 考虑virtual函数以外的其他选择 { /* 1: ...
- Yeoman的好基友:Grunt
grunt介绍 前端不能承受之痛 1.这是我们的生活 文件压缩:YUI Compressor.Google Closure 文件合并:fiddler + qzmin 文件校验:jshint 雪碧图:c ...
- centos7 源码部署LNMP
一.环境 系统环境:centos 7.4 64位 Nginx:1.7.9 MySQL: 5.7.20 (二进制包) PHP:5.6.37 二.Ngin 安装 Nginx部署 yum install ...
- GitHub 新手教程 三,Git Bash
1,通过 开始菜单 启动 Git Bash,或者 在 cmd 下执行以下命令: D:\SoftWare\Git\git-bash.exe --cd-to-home (D:\SoftWare\Git 是 ...
- 教你用PS制作雨天窗户上透明水滴字
雨天窗户上透明水滴字制作方法很简单,主要利用图层样式来实现.学习后可以让你对图层样式有更好的了解,认识. 先看下完成后的效果图: 步骤1: 在Photoshop中我们新建或Ctrl+N,创建1920x ...
- PAT甲题题解-1127. ZigZagging on a Tree (30)-中序、后序建树
根据中序遍历和前序遍历确定一棵二叉树,然后按“层次遍历”序列输出.输出规则:除根节点外,接下来每层的节点输出顺序是:先从左到右,再从右到左,交替输出 #include <iostream> ...
- Linux内核分析——第六周学习笔记
进程的描述和进程的创建 前言:以下笔记除了一些讲解视频中的概念记录,图示.图示中的补充文字.总结.分析.小结部分均是个人理解.如有错误观点,请多指教! PS.实验操作会在提交到MOOC网站的博客中写.