数位DP小结
来源:互联网 发布:家常菜谱软件下载 编辑:程序博客网 时间:2024/06/03 13:09
数位DP小结
概述
一般是求小于等于数字N的某些特征数字个数,或者是区间[L,R]的之间的某些特征数字个数,后者一般可以转换成求差的方式来做。
模板
数字处理函数:
int f(int num){ int ans; int pos = 0; while(num){ digit[++pos]=num%10; num=num/10; } return dfs( pos, s , true );}
其中:
digit为处理串的每个数位的数值。
s为初始的状态。
如果有其他限定条件,dfs
中可以增加参数。
DFS
函数:
int dfs(int l, int s, bool jud) { if ( l==-1 ) return s == target_s; if ( !e && ~f[l][s] ) return f[l][s]; int ans = 0; int nex = e ? digit[i] : 9; for (int d = 0; d <= nex; ++d) ans += dfs( l-1 , new_s( s,d ) , e && d==nex ); return jud ? ans : f[l][s] = ans;}
其中:
f为记忆化数组;
l为当前处理串的第l位(权重表示法,也即后面剩下l+1位待填数);
s为之前数字的状态(如果要求后面的数满足什么状态,也可以再记一个目标状态t之类,for的时候枚举下t);
jud表示之前的数是否是上界的前缀(即后面的数能否任意填)。
for循环枚举数字时,要注意是否能枚举0,以及0对于状态的影响,有的题目前导0和中间的0是等价的,但有的不是,对于后者可以在dfs时再加一个状态变量z,表示前面是否全部是前导0,也可以看是否是首位,然后外面统计时候枚举一下位数。It depends.
于是关键就在怎么设计状态。当然做多了之后状态一眼就可以瞄出来。
前导零的判断:
int dfs(bool zero)......ans+=dfs(zero&&i==0)//不区分前导零与零ans+=dfs(zero&&i==0&&l>1)//区分前导零与零
题目:
A. 【CF55D】
题意:
给定区间[L,R],求区间完美数字的个数。(一个数字是完美数字当且仅当该数字可整除其所有数位上的非零数)
思路:
位数上的数字只需要考虑2~9,因此用一个数字1<<8
来表示有哪些数字出现过。
如果一个数是所有位数的倍数,那么一定是其最小公倍数的倍数,其所有的最小公倍数是2520,所以求和的时候直接对2520取余。dp[l][cnt][sum]
,l为数字长度,cnt为位数上有哪些数,sum为数字和。dfs(int l,int cnt,int sum,bool jud)
,jud为是否为边界。
代码:
#include <cstdio>#include <cstring>#include <iostream>using namespace std;typedef long long LL;int digit[20];LL dp[20][(1<<8)][2520];LL dfs(int l,int cnt,int sum,bool jud){ if(l==0){ for(int i=2;i<=9;i++) if( (cnt&(1<<(i-2))) && (sum%i) ){ return 0; } return 1; } if(!jud && dp[l][cnt][sum] != -1) return dp[l][cnt][sum]; int nes = jud ? digit[l] : 9; LL pos = 0 ; for(int i=0;i<=nes;i++){ pos += dfs(l-1 , i<2 ? cnt : (cnt|(1<<(i-2))) , ((sum*10)+i)%2520 , jud&&(i==digit[l])); } if(!jud) dp[l][cnt][sum]=pos; return pos;}LL f(LL k){ LL ans; int pos = 0; while(k){ digit[++pos]=k%10; k=k/10; } ans = dfs(pos,0,0,true); return ans;}int main(){ #ifdef LOCAL freopen("in.txt","r",stdin); freopen("out.txt","w",stdout); #endif memset(dp,-1,sizeof(dp)); int t; LL m,n; cin>>t; while(t--){ cin>>m>>n; cout<<f(n)-f(m-1)<<endl; }return 0;}
B. 【HDU4352】
题意:
寻找[L,R]中间所有数字串中LIS(最长上升子序列)为K的数字和。
思路:
LIS运用动态规划可以在O(nlogn)的时间复杂度解决,此略。
因为最多只有0~9十个数字,因此可以预处理。
sta为LIS的状态,siz[sta]中保存LIS的长度(即二进制中1的个数),nex[sta][l]为在sta中插入数字l之后的LIS状态。dp[l][sta][k]
:l为数字长度,sta为当前LIS的状态,k为所要求的LIS长度。dfs(int l,int sta,bool zero,bool jud)
:zero判断是否为前导零,jud为是否为边界。
注意:dp[l][sta][k]中,k并不是必须的,然而因为本题样例组数过多且k很小,所以选择增加一维表状态而不是每次都初始化以节约时间。
代码:
#include <cstdio>#include <cstring>#include <iostream>using namespace std;typedef long long LL;int digit[20],siz[1<<10],nex[1<<10][10];LL dp[66][(1<<10)][11];int Cas,k;LL dfs(int l,int sta,bool zero,bool jud){ if(l==0) return siz[sta]==k; if(!jud&&dp[l][sta][k]!=-1)return dp[l][sta][k]; int nes = jud ? digit[l] : 9; LL ans = 0; for(int i=0;i<=nes;i++) ans+= dfs(l-1, (zero&&i==0) ? 0 : nex[sta][i] , zero&&i==0 , jud&&i==nes); if(!jud)dp[l][sta][k]=ans; return ans;}LL f(LL num){ int pos = 0; while(num){ digit[++pos]=num%10; num/=10; } return dfs(pos,0,true,true);}int find_nex(int status,int num){ for(int i=num;i<10;i++) if (status&(1<<i)) return (status^(1<<i)|(1<<num)); return status|(1<<num);}int main(){ #ifdef LOCAL freopen("in.txt","r",stdin); freopen("out.txt","w",stdout); #endif memset(dp,-1,sizeof(dp)); for(int i=0;i<(1<<10);i++){ siz[i]=0; for(int j=0;j<10;j++){ if(i&(1<<j))siz[i]++; nex[i][j]=find_nex(i,j); } } int t; LL m,n; cin>>t; while(t--){ cin>>m>>n>>k; cout<<"Case #"<<++Cas<<": "; cout<<f(n)-f(m-1)<<endl; }return 0;}
C. 【HDU2089】
题意:
[L,R]中,不含4或62的数字个数。
思路:
dp[l][six]
:l为数字长度,six为最后一位数字是否为6。dfs(int l,bool six,bool jud)
,jud判断是否为边界。
代码:
#include <cstdio>#include <cstring>#include <iostream>using namespace std;int digit[10],dp[10][2],vis[10][2];int dfs(int l,bool six,bool jud){ if(l==0) return 1; if(!jud&&vis[l][six])return dp[l][six]; int len = jud ? digit[l] : 9; int nes = 0; for(int i=0;i<=len;i++){ if((i==4)||(six&&i==2)) continue; nes += dfs(l-1 , i==6 , jud&&(i==len)); } if(!jud){ vis[l][six]=true; dp[l][six]=nes; } return nes;}int f(int k){ memset(dp,0,sizeof(dp)); memset(vis,0,sizeof(vis)); int pos = 0; while(k){ digit[++pos]=k%10; k=k/10; } int ans = dfs(pos,false,true); return ans;}int main(){ #ifdef LOCAL freopen("in.txt","r",stdin); freopen("out.txt","w",stdout); #endifint m,n; while(scanf("%d%d",&m,&n)&&(m+n)){ cout<<f(n)-f(m-1)<<endl; }return 0;}
D. 【HDU3555】
题意:
数字中含有49的数字个数。
思路:
偷了下懒,用C题的代码求不含49的个数,然后做差,居然过了= =b。其实这道题打表都行= =b。
代码:
#include <cstring>#include <cstdio>#include <iostream>#define LL long longusing namespace std;int digit[25];LL dp[25][2];LL dfs(int l,bool num,bool jud){ if(l==0) return 1; if(!jud && dp[l][num]!=-1) return dp[l][num]; LL ans=0; int nex = jud ? digit[l] : 9; for(int i=0;i<=nex;i++){ if(num&&i==9)continue; ans += dfs(l-1,i==4,jud&&i==nex); } if(!jud) dp[l][num]=ans; return ans;}LL f(LL num){ int pos = 0; while(num){ digit[++pos]=num%10; num/=10; } return dfs(pos,false,true);}int main(){ #ifdef LOCAL freopen("in.txt","r",stdin); freopen("out.txt","w",stdout); #endif int T; LL n; memset(dp,-1,sizeof(dp)); scanf("%d",&T); while(T--){ cin>>n; cout<<n-f(n)+1<<endl; } return 0;}
E. 【HDU3252】
题意:
数字[L,R]中,round number数字的个数。round number即数字转换成二进制后0的个数大于等于1的个数。
思路:
digit数组中直接保存该数字的二进制形式。dp[l][cnt1][cnt2][zero]
:l为数字长度,cnt1为数字0的数字个数,cnt2为数字1的数字个数,zero判断是否为前导零。dfs(int l,int cnt1,int cnt2,bool zero,bool jud)
:jud判断是否为边界。
代码:
#include <cstdio>#include <cstring>#include <iostream>using namespace std;int digit[35],dp[35][35][35][2],pos;int dfs(int l,int cnt1,int cnt2,bool zero,bool jud){ if(l==0) return cnt1>=cnt2; if(!jud&&dp[l][cnt1][cnt2][zero]!=-1) return dp[l][cnt1][cnt2][zero]; int nex = jud ? digit[l] : 1; int ans = 0; for(int i=0;i <= nex;i++){ ans += dfs( l-1 , zero ? 0 : cnt1+(i==0) ,cnt2+(i==1) , zero&&(i==0) , jud&&i==nex ); } if(!jud)dp[l][cnt1][cnt2][zero]=ans; return ans;}int f(int num){ pos = 0; while(num){ digit[++pos]=num%2; num>>=1; } return dfs(pos,0,0,true,true);}int main(){ #ifdef LOCAL freopen("in.txt","r",stdin); freopen("out.txt","w",stdout); #endif memset(dp,-1,sizeof(dp)); int m,n; cin>>m>>n; cout<<f(n)-f(m-1)<<endl; return 0;}
F. 【HDU3709】
题意:
统计区间[L,R]中,balanced number的数字个数。balanced number即对于任意一个数字串,假设其有一个数字位,它左边的数字乘距离的和等于它右边的数字乘距离的和,则其为balanced number。
思路:
枚举作为平衡点的数字位数,最后要减掉(pos-1)因为对于0,计算了0,00,000...重复计算了pos次,只需要保留一次。dp[l][o][pos]
:长度为l,平衡点位置为o时的当前状态(pos为0时表示平衡,pos>0则为左边的沉,pos<0则为右边的沉)dfs(int l,int o,int pos,bool jud)
:jud判断是否为边界。
注意只要pos<0就可以返回false。
代码:
#include <cstdio>#include <cstring>#include <iostream>using namespace std;#define LL long longint digit[20];LL dp[20][20][2000];LL dfs(int l,int o,int pos,bool jud){ if( l==0 ) return pos == 0; if( pos<0 ) return 0; if(!jud && dp[l][o][pos] >= 0)return dp[l][o][pos]; int nex = jud ? digit[l] : 9; LL ans = 0; for(int i=0;i<=nex;i++) ans += dfs( l-1 , o , pos+i*(l-o) , jud && i==nex ); return jud ? ans : dp[l][o][pos] = ans;}LL f(LL num){ int pos = 0; while(num){ digit[++pos]=num%10; num/=10; } LL ans = 0; for(int i=1;i<=pos;i++) ans += dfs(pos,i,0,true); ans = ans - pos + 1; return ans;}int main(){ #ifdef LOCAL freopen("in.txt","r",stdin); freopen("out.txt","w",stdout); #endif memset(dp,-1,sizeof(dp)); int T; LL m,n; cin>>T; while(T--){ cin>>m>>n; cout<<f(n)-f(m-1)<<endl; } return 0;}
G. 【HDU3652】
题意:
[1,n]的含数字13且为13的倍数的数字个数。
思路:
dp[l][mod][iso][has]
:l为数字长度,mod为当前数字对13的取余值,iso为是否存在,has为最后一位是否为数字1。int dfs(int l,int mod,bool iso,bool has,bool jud)
:jud判断是否为边界值。
代码:
#include <cstdio>#include <cstring>#include <iostream>using namespace std;int digit[11];int dp[11][13][2][2];int dfs(int l,int mod,bool iso,bool has,bool jud){ if(l==0)return (has && !mod); if(!jud && dp[l][mod][iso][has])return dp[l][mod][iso][has]; int nex = jud ? digit[l] : 9; int ans = 0; for(int i=0;i<=nex;i++){ ans += dfs( l-1 , (mod*10+i)%13 , i==1 , has||(iso&&i==3) , jud && i==nex ); } return jud ? ans : dp[l][mod][iso][has]=ans;}int f(int num){ int pos = 0; while(num){ digit[++pos]=num%10; num/=10; } return dfs(pos,0,false,false,true);}int main(){ #ifdef LOCAL freopen("in.txt","r",stdin); freopen("out.txt","w",stdout); #endif int n; while(~scanf("%d",&n)){ printf("%d\n",f(n)); } return 0;}
H. 【HDU4734】
题意:
对于每个数字x,都有一个F(x)值,让你求[0,B]中,函数值小于等于F(A)的数字个数。
思路:
首先计算出F(a)。dp[l][sum]
:l为当前数字长度,sum为F(A)减去之前枚举的数字的数字差(如果差为正则代表F(A)大)。dfs(int l,int sum,bool jud)
:jud判断是否为边界。
代码:
#include <cstdio>#include <cstring>#include <iostream>using namespace std;int digit[10],dp[10][100005],CASE;int dfs(int l,int sum,bool jud){ if( l==0 ) return sum >= 0; if( sum<0 )return 0; if( !jud && dp[l][sum]>=0 ) return dp[l][sum]; int nex = jud ? digit[l] : 9; int ans = 0; for(int i=0 ; i<=nex ; i++) ans += dfs( l-1 , sum-i*(1<<(l-1)) , jud&&i==nex ); return jud ? ans : dp[l][sum] = ans;}inline int f(int num,int cas){ int pos = 0; while(num){ digit[++pos]=num%10; num/=10; } return dfs(pos,cas,true);}inline int count(int a){ int pos = 0,ans = 0; while(a){ ans+=(a%10)*(1<<pos++); a/=10; } return ans;}int main(){ #ifdef LOCAL freopen("in.txt","r",stdin); freopen("out.txt","w",stdout); #endif memset(dp,-1,sizeof(dp)); int T,a,b,cas; scanf("%d",&T); while(T--){ scanf("%d%d",&a,&b); cas = count(a); //cout<<">"<<cas<<endl; printf("Case #%d: ",++CASE); printf("%d\n",f(b,cas)); } return 0;}
I. 【ZOJ3494】
题意:
区间[L,R]的数字转换成BCD
之后,不含有forbidden code(即长度不超过20的01组成的数字串)的数字个数。
思路:
forbidden code可以用trie树
储存。dp[l][pos]
:l为数字长度,pos为树上的位置。dfs(int l,int pos,bool zero,bool jud)
:zero判断是否为前导零(注意和数字0区分),jud判断是否为边界。
代码:
#include<iostream>#include<cstdio>#include<cstring>#include<cmath>#include<algorithm>using namespace std;const int M = 10005,MOD=1000000009;#define LL long longint idx;char str[25];int bcd[2005][10];LL dp[205][2005];int digit[205],len,n;struct Trie{ Trie *next[2]; Trie *fail; int isword,kind;};Trie *que[M],s[M];Trie *NewNode(){ Trie *tmp=&s[idx]; tmp->next[0]=tmp->next[1]=NULL; tmp->isword=0; tmp->fail=NULL; tmp->kind=idx++; return tmp;}void Insert(Trie *root,char *s,int l){ Trie *p=root; for(int i=0; i<l; i++){ if(p->next[s[i]-'0']==NULL) p->next[s[i]-'0']=NewNode(); p=p->next[s[i]-'0']; } p->isword=1;}void Bulid_fail(Trie *root){ int head=0,tail=0; que[tail++]=root; root->fail=NULL; while(head<tail){ Trie *tmp=que[head++]; for(int i=0; i<2; i++){ if(tmp->next[i]){ if(tmp==root) tmp->next[i]->fail=root; else{ Trie *p=tmp->fail; while(p!=NULL){ if(p->next[i]){ tmp->next[i]->fail=p->next[i]; break; } p=p->fail; } if(p==NULL) tmp->next[i]->fail=root; } if(tmp->next[i]->fail->isword) tmp->next[i]->isword=tmp->next[i]->fail->isword; que[tail++]=tmp->next[i]; } else if(tmp==root) tmp->next[i]=root; else tmp->next[i]=tmp->fail->next[i]; } }}//状态当前在状态pre,经过一个数字num之后到达哪个状态//如果不合法,返回-1int BCD(int pre,int num){ if(s[pre].isword) return -1; int cur=pre; for(int i=3;i>=0;i--){ int k=(num>>i)&1; if(s[cur].next[k]->isword) return -1; else cur=s[cur].next[k]->kind; } return cur;}void Get_next(){ for(int i=0;i<idx;i++){ for(int j=0;j<10;j++){ bcd[i][j]=BCD(i,j); } }}LL dfs(int l,int pos,bool zero,bool jud){ if(pos<0) return 0; if(l==0)return 1; if(!jud&&dp[l][pos]>=0)return dp[l][pos]; LL ans = 0; int nex = jud ? digit[l] : 9; for(int i=0;i<=nex;i++) ans += dfs( l-1 , zero&&i==0&&l>1 ? pos : bcd[pos][i] , zero&&i==0 , jud&&i==nex ); ans%=MOD; return jud ? ans : dp[l][pos]=ans;}LL cal(char *s,int l){ memset(dp,-1,sizeof(dp)); for(int i=1;i<=l;i++) digit[l-i+1]=s[i-1]-'0'; dfs(l,0,true,true);}char A[205],B[205];void sub(char *s,int len){ for(int i=len-1;i>=0;i--){ if(s[i]=='0') s[i]='9'; else{ s[i]--; break; } }}int main(){ #ifdef LOCAL freopen("in.txt","r",stdin); freopen("out.txt","w",stdout); #endif int t; scanf("%d",&t); while(t--){ idx=0; Trie *root=NewNode(); scanf("%d",&n); for(int i=1; i<=n; i++){ scanf("%s",str); Insert(root,str,strlen(str)); } Bulid_fail(root); Get_next(); scanf("%s",A); sub(A,strlen(A)); scanf("%s",B); cout<<((cal(B,strlen(B))-cal(A,strlen(A)))%MOD+MOD)%MOD<<endl; } return 0;}
J. 【HDU4507】
题意:
区间[L,R]中,和7无关的数字的平方和是多少?含7的数、数位和为7的倍数、数为7的倍数都是和7有关的数字。
思路:
dp[len][sum][remain]
:len为长度,sum为数位和,remain为前缀和。dfs(int len,int sum,int remain,bool jud)
jud判断是否为边界。
维护三个数字:个数,和,平方和。a[1]^2 + a[2]^2 + … + a[n]^2,新式是 (a[1]+b)^2 + (a[2]+b)^2 + … + (a[n]+b)^2
,按照这样的原理求前缀的平方和。
代码:
#include <cstdio>#include <cstring>#include <iostream>using namespace std;typedef long long LL;typedef pair< int,pair<LL,LL> > PILL;//< count , < sum,square sum> >#define F first#define S second#define MP make_pairconst int mod = 1e9+7, mx = 20;int digit[mx];LL pow10[20];PILL dp[20][7][7];bool vis[20][7][7];//边界的答案不同PILL dfs(int len,int sum,int remain,bool jud){ if(!len) if(sum&&remain) return MP(1,MP(0LL,0LL)); else return MP(0,MP(0LL,0LL)); if(!jud&&vis[len][sum][remain]) return dp[len][sum][remain]; PILL res = MP(0,MP(0LL,0LL)); int nex = jud ? digit[len] : 9; for(int i=0;i<=nex;i++){ if(i==7) continue; PILL ans = dfs(len-1,(sum+i)%7,(remain*10+i)%7,jud&&i==nex); LL pref = i * pow10[len-1] % mod; res.F += ans.F; res.F %= mod; res.S.F += ans.S.F + pref * ans.F; res.S.F %= mod; res.S.S += ans.S.S + pref * pref % mod * ans.F + 2 * pref * ans.S.F; res.S.S %= mod; } if(!jud){ vis[len][sum][remain]=true; dp[len][sum][remain]=res; } return res;}LL f(LL a){ memset(vis,0,sizeof(vis)); int len=0; while(a){ digit[++len]=a%10; a=a/10; } return dfs(len,0,0,true).S.S;}int main(){ #ifdef LOCAL freopen("in.txt","r",stdin); freopen("out.txt","w",stdout); #endif pow10[0]=1; for(int i=1;i<20;i++) pow10[i]=pow10[i-1]*10%mod; int t; scanf("%d",&t); while(t--){ LL a,b,ans; scanf("%lld%lld",&a,&b); ans = (f(b)-f(a-1)+mod)%mod; printf("%lld\n",ans); } return 0;}
K. 【SPOJ10606】
题意:
求[L,R]中positive integer的数字个数。positive integer就是对于一个数字串,偶数数字各有奇数个,奇数数字各有偶数个。
思路:
用一个三进制数字表示每个数字的状态,0为未出现过,1为出现过奇数次,2为出现过偶数次。dp[l][s]
:l为数字长度,s为当前所有数字出现的状态。dfs(int l,int s,bool zero,bool jud)
:zero判断是否为前导零,jud判断是否为边界。
代码:
#include <cstdio>#include <cstring>#include <iostream>using namespace std;#define LL long longint dp[20][60000];int digit[20];LL pos[11];void init(){ pos[0]=1; for(int i=1;i<=10;i++) pos[i]=pos[i-1]*3;}bool judge(int s){ for(int i=9;i>=0;i--){ if(i%2==0 && s/pos[i]==2) return false; if(i%2==1 && s/pos[i]==1) return false; s%=pos[i]; } return true;}LL dfs(int l,int s,bool zero,bool jud){ if(l==0){ if(judge(s)) return 1; return 0; } if(!jud && dp[l][s]>=0)return dp[l][s]; int nes = jud ? digit[l] : 9; LL ans = 0; for(int i=0;i<=nes;i++) ans += dfs(l-1 , zero&&i==0 ? 0 : ((s%pos[i+1])/pos[i]==2 ? s-pos[i] : s+pos[i]), zero&&i==0 , jud&&i==nes); return jud ? ans : dp[l][s] = ans;}LL f(LL num){ int pos=0; while(num){ digit[++pos]=num%10; num/=10; } return dfs(pos,0,true,true);}int main(){ #ifdef LOCAL freopen("in.txt","r",stdin); freopen("out.txt","w",stdout); #endif init(); int T; LL a,b; cin>>T; memset(dp,-1,sizeof(dp)); while(T--){ cin>>a>>b; cout<<f(b)-f(a-1)<<endl; } return 0;}
参考资料
刘聪《浅谈数位类统计问题》
高逸涵《数位计数问题解法研究》
http://www.cnblogs.com/jffifa/archive/2012/08/17/2644847.html
- 数位DP小结
- 数位DP小结
- 基础数位DP小结
- 数位DP小结
- 数位DP小结
- 数位DP小结
- 数位dp小结
- 数位DP学习小结
- 数位dp小结
- 数位DP小结
- 【算法笔记】数位dp小结
- 数位DP小结(阶段性)
- (原创)数位DP专题小结--by sgx
- 数位DP专题小结--by sgx
- 数位DP专题小结--by sgx 数位DP专题小结--by sgx
- 数位DP小结_记忆化搜索版
- 数位dp小结(状压的第一次试水)
- 数位dp
- mongoose模糊查询+限制条件
- [PAT-乙级]1046.划拳
- 扩展ThreadPoolExecutor
- C# 反射
- 虚拟机Ubuntu下使用串口短信猫
- 数位DP小结
- 【shell】判断文件中是否存在字符串
- System.getProperty()参数大全
- 环境搭建,与入门
- windows cmd javac乱码
- 公共技术点之 Java 注解 Annotation
- python 变量命名规范
- [PAT-乙级]1047.编程团体赛
- JavaScript-BOM模型