数位DP
Posted Achen
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了数位DP相关的知识,希望对你有一定的参考价值。
刚学DP的时候觉得数位DP非常难,后来发现还是套路比较深。
1.
BZOJ1833 count 数字计数.cpp
写的第一个,非递归版。感觉非递归的数位Dp比较巧妙,也还是比较套路
先预处理,然后统计比最高位位数小的树的答案,统计最高位位数的答案时,先统计不到lim的,再往下一层统计lim的
//Twenty
#include<cstdio>
#include<cstdlib>
#include<iostream>
#include<algorithm>
#include<cmath>
#include<cstring>
#include<queue>
#include<vector>
using namespace std;
typedef long long LL;
int lim[20];
LL l,r,power=1,dp[20][20][20],ans[2][10];
void pre(){
for(int i=0;i<10;i++)
dp[1][i][i]=1;
for(int i=2;i<=13;i++){
power*=10;
for(int j=0;j<10;j++){
dp[i][j][j]+=power;
for(int k=0;k<10;k++)
for(int l=0;l<10;l++)
dp[i][j][l]+=dp[i-1][k][l];
}
}
}
void calu(LL x,int p){
int k=0;
if(!x) lim[++k]=0;
while(x){
lim[++k]=x%10;
x/=10;
}
LL power=1,cnt=1;
for(int i=1;i<=k;i++){
ans[p][lim[i]]+=cnt;
cnt+=lim[i]*power;
power*=10;
for(int j=(i==k&&i!=1);j<lim[i];j++)
for(int kk=0;kk<10;kk++)
ans[p][kk]+=dp[i][j][kk];
for(int j=(i!=2);j<10;j++)
for(int kk=0;kk<10;kk++)
ans[p][kk]+=dp[i-1][j][kk];
}
}
int main()
{
pre();
scanf("%lld%lld",&l,&r);
if(l>=1) calu(l-1,0);
calu(r,1);
for(int i=0;i<10;i++){
if(i) putchar(‘ ‘);
printf("%lld",ans[1][i]-ans[0][i]);
}
return 0;
}
2.BZOJ 1026 windy数
套路跟上一题差不多,几乎抄的学长的标程感觉比较坑,对1的处理有问题,但问题不大这道题数据可以过。
//Twenty
#include<cstdio>
#include<cstdlib>
#include<iostream>
#include<algorithm>
#include<cmath>
#include<cstring>
#include<queue>
#include<vector>
using namespace std;
const int maxn=2000000000+299;
const int maxl=12;
int l,r,dp[maxl][20],base[maxl];
void pre(){
for(int i=0;i<10;i++) dp[1][i]=1;
for(int i=2;i<maxl;i++)
for(int j=0;j<10;j++)
for(int k=0;k<10;k++)
if(abs(j-k)>=2) dp[i][j]+=dp[i-1][k];
base[1]=1;
for(int i=2;i<maxl;i++) base[i]=base[i-1]*10;
}
int cal(int x){
int res=0;
int w=10;
if(!x) return 0;
while(base[w]>x) w--;
for(int i=1;i<w;i++)
for(int j=1;j<10;j++)
res+=dp[i][j];
int cur,pre;
pre=x/base[w];
for(int i=1;i<pre;i++)
res+=dp[w][i];
x%=base[w];
for(int i=w-1;i;i--){
cur=x/base[i];
x%=base[i];
for(int j=0;j<cur;j++)
if(abs(pre-j)>=2) res+=dp[i][j];
if(i==1){
int j=cur;
if(abs(pre-j)>=2)res+=dp[i][cur];}
if(abs(pre-cur)<2) break;
pre=cur;
}
return res;
}
int main()
{
pre();
scanf("%d%d",&l,&r);
printf("%d",cal(r)-cal(l-1));
return 0;
}
3.
BZOJ 3209 花神的数论题
对于我这种ZZ来说有点难理解
我们考虑1出现了k次的数有多少个,就可以用组合数来做。
预处理出组合数,然后从高位到低位扫,依照前两题的套路走
for(int i=(cnt==0);i<top;i++)
表示最高位是0的答案,然后往后计算最高位是1的答案
cnt表示前面已经有了多少个1,这些都是统计达到lim的答案
然后用组合数,快速幂。
//Twenty
#include<cstdio>
#include<cstdlib>
#include<iostream>
#include<algorithm>
#include<cmath>
#include<cstring>
#include<queue>
#include<vector>
typedef long long LL;
const int mod=10000007;
LL cnt,n,C[70][70],ans=1;
int w[70],top;
using namespace std;
LL ksm(LL a,LL b){
LL base=a,res=1;
while(b){
if(b&1) (res*=base)%=mod;
(base*=base)%=mod;
b>>=1;
}
return res;
}
int main()
{
scanf("%lld",&n);
for(;n;n>>=1) w[++top]=n&1;
for(int i=0;i<=65;i++) C[i][0]=1;
for(int i=1;i<=65;i++)
for(int j=1;j<=i;j++)
C[i][j]=C[i-1][j]+C[i-1][j-1];
for(;top;top--) if(w[top]){
for(int i=(cnt==0);i<top;i++)
(ans*=ksm((cnt+i),C[top-1][i]))%=mod;
cnt++;
}
printf("%lld\n",(ans*cnt)%mod);
return 0;
}
接下来就开始优美的记忆化搜索了,感觉记忆化搜索非常好用,套路很深,直接无脑套板,据说也不比非递归慢多少4521 手机号码
4、BZOJ 4521 手机号码
一道一看就知道怎么转移的简单题,但是用dp就。。非常难写,自己傻逼地写了个98行的WA了,网上找了一个九个for套8个if的,简直可怕。
而用记忆化搜索就非常方便了。
//Twenty
#include<cstdio>
#include<cstdlib>
#include<iostream>
#include<algorithm>
#include<cmath>
#include<cstring>
#include<queue>
#include<vector>
using namespace std;
typedef long long LL;
LL l,r,ansl,ansr;
int lim[20],dp[20][20][20][20][20];
LL dfs(int pos,int now,int fo,int eig,int cnt,int limit){
if(!pos) return cnt==2;
if(!limit&&dp[pos][now][fo][eig][cnt]) return dp[pos][now][fo][eig][cnt];
int up=limit?lim[pos]:9;
LL res=0;
for(int i=(pos==11);i<=up;i++){
if((eig&&i==4)||(fo&&i==8)) continue;
if(cnt==2) res+=dfs(pos-1,i,fo||i==4,eig||i==8,cnt,limit&&i==lim[pos]);
else
res+=dfs(pos-1,i,fo||i==4,eig||i==8,(i==now?cnt+1:0),limit&&i==lim[pos]);
}
if(!limit) dp[pos][now][fo][eig][cnt]=res;
return res;
}
void cal(LL x,LL &ans){
LL tp=x;
for(int i=1;i<=11;i++){
lim[i]=tp%10;
tp/=10;
}
ans=dfs(11,0,0,0,0,1);
}
int main()
{
scanf("%lld%lld",&l,&r);
if(l!=10000000000) cal(l-1,ansl);
cal(r,ansr);
printf("%lld\n",ansr-ansl);
return 0;
}
套路 :
如果搜到底了就返回是否满足要求。
否则,若是没有到limit且已经搜过了,就返回记忆化的值。
否则就重新搜一遍。
搜完返回值,若是不是Limit就把这个结果记忆化。
以上是关于数位DP的主要内容,如果未能解决你的问题,请参考以下文章