数位DP

简单题

1.BZOJ1833 count 数字计数

写的第一个,非递归版。感觉非递归的数位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;
}
BZOJ 1833 count 数字计数

-------------19-3-28upd------------------

已经不会写数位dp了,自己yy着写了个,调了好久。。

//Achen
#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdlib>
#include<vector>
#include<cstdio>
#include<queue>
#include<cmath>
#define For(i,a,b) for(int i=(a);i<=(b);i++)
#define Rep(i,a,b) for(int i=(a);i>=(b);i--)
const int N=14;
typedef long long LL;
typedef double db;
using namespace std;
LL l,r,dp[N][N][N],ans[N],power[N];
int lim[N];

template<typename T>void read(T &x)  {
    char ch=getchar(); x=0; T f=1;
    while(ch!='-'&&(ch<'0'||ch>'9')) ch=getchar();
    if(ch=='-') f=-1,ch=getchar();
    for(;ch>='0'&&ch<='9';ch=getchar()) x=x*10+ch-'0'; x*=f;
}

void pre() {
    power[1]=1;
    For(i,0,9) dp[1][i][i]=1;
    For(i,2,13) {
        power[i]=power[i-1]*10;
        For(j,0,9) {
            For(k,0,9) 
                For(l,0,9) 
                    dp[i][j][l]+=dp[i-1][k][l];    
            dp[i][j][j]+=power[i];
        }
    }
}

void solve(LL n,int f) {
    LL tp=n,tpp=n; lim[0]=0;
    if(n>=0) ans[0]+=f;
    if(!n) return;
    while(tp) {
        lim[++lim[0]]=tp%10;
        tp/=10;
    }
    For(i,1,lim[0]-1) 
        For(j,1,9) For(k,0,9) ans[k]+=f*dp[i][j][k];
    Rep(i,lim[0],1) {
        tpp=tpp%power[i];
        int dn=(i==lim[0]),up=lim[i];
        For(j,0,9) For(k,0,9) ans[k]+=f*(up-dn)*dp[i-1][j][k]; 
        For(j,dn,up-1) ans[j]+=f*power[i];
        ans[lim[i]]+=f*(tpp+1);
    }
}

int main() {
#ifdef DEBUG
    freopen(".in","r",stdin);
    freopen(".out","w",stdout);
#endif
    read(l); read(r);
    pre();
    solve(l-1,-1); 
    solve(r,1); 
    For(i,0,9) printf("%lld ",ans[i]);
    return 0;
}
View Code

 

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;
}
BZOJ 1026 windy数

 

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;
}
BZOJ 3209 花神的数论题
 
 

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;
}
BZOJ 4521 手机号码

套路 :

如果搜到底了就返回是否满足要求。

否则,若是没有到limit且已经搜过了,就返回记忆化的值。

否则就重新搜一遍。

搜完返回值,若是不是Limit就把这个结果记忆化。

 

5.BZOJ 3679 数字之积

很套路的数位DP

关键在于预处理出所有可能的乘积,爆搜是9^18好像,反正几年跑出来(SXY大佬帮我算的这个)

根据唯一分解定理,因为所有数都是1~9,所以只要枚举2^a*3^b*5^c*7^d就可以预处理出来了。

//Twenty
#include<cstdio>
#include<cstdlib>
#include<iostream>
#include<algorithm>
#include<cmath>
#include<cstring>
#include<queue>
#include<vector>
#include<set>
using namespace std;
typedef long long LL;
const int maxn=1e9;
LL tp,n,a[6000],dp[20][6000],sz,l,r,ansl,ansr;
int lim[20],cnt;
void pre(){
    for(int i=0;i<=30;i++)
      for(int j=0;j<=19;j++)
        for(int k=0;k<=13;k++)
          for(int l=0;l<=11;l++){
             LL res=pow(2,i)*pow(3,j)*pow(5,k)*pow(7,l);
             if(res>0&&res<=n) a[++sz]=res;
        }
    sort(a+1,a+sz+1);
} 
LL dfs(int pos,int sum,int limit,int flag){
    if(!pos) return 1;
    if(!limit&&~dp[pos][sum]) return dp[pos][sum];
    int up=limit?lim[pos]:9; 
    LL res=0;
    for(int i=(!flag||(flag&&pos==1));i<=up;i++){
        LL realsum=a[sum];
        if(flag) realsum=1;
        if(realsum*i>n) break;
        if(realsum*i<=n){
        int now=lower_bound(a,a+sz+1,realsum*i)-a;
        res+=dfs(pos-1,now,limit&&i==lim[pos],flag&&i==0);
        }
    }
    if(!limit) dp[pos][sum]=res;
    return res;
}
void cal(LL x,LL &ans){
    if(!x) {return;}
    memset(dp,-1,sizeof(dp));
    cnt=0; tp=x;
    while(tp) {lim[++cnt]=tp%10; tp/=10;}
    ans+=dfs(cnt,1,1,1);
}
int main()
{
    scanf("%lld%lld%lld",&n,&l,&r);
    pre(); 
    cal(l-1,ansl);
    cal(r-1,ansr);
    printf("%lld\n",ansr-ansl);
    return 0;
}
BZOJ 3679 数字之积

 

6.BZOJ 1799 self 同类分布

要求模所有位的数字之和为0的数的个数,因为所有和只有162,所以可以枚举这个模数,然后记忆化搜索

dp[i][j][k]表示在当前模数下,长度为i,数字和为j,数字模模数为k的数的个数

//Twenty
#include<cstdio>
#include<cstdlib>
#include<iostream>
#include<algorithm>
#include<cmath>
#include<cstring>
#include<queue>
#include<vector>
using namespace std;
const int maxn=18*9;
typedef long long LL;
int lim[20],n,mod;
LL ans,ansl,ansr,l,r,dp[20][maxn][maxn];
LL dfs(int pos,int sum,int val,int limit){
    if(sum>mod) return 0;
    if(!pos) return (sum==mod&&val==0);
    if(!limit&&~dp[pos][mod-sum][val]) return dp[pos][mod-sum][val];
    int li=limit?lim[pos]:9; 
    LL res=0;
    for(int i=0;i<=li;i++)
    res+=dfs(pos-1,sum+i,(val*10+i)%mod,(limit&&i==li));
    if(!limit) dp[pos][mod-sum][val]=res;
    return res;
}
void cal(LL x){
    if(!x) return ; LL tp=x; n=0;
    while(tp) {lim[++n]=tp%10;tp/=10;} 
    for(int i=1;i<=n*9;i++){
        if(i>x) break;
        memset(dp,-1,sizeof(dp));
        mod=i;
        ans+=dfs(n,0,0,1);
    }
}
int main()
{

    scanf("%lld%lld",&l,&r);
    cal(r); 
    ansr=ans; ans=0;
    cal(l-1);
    ansl=ans;
    printf("%lld\n",ansr-ansl);
    return 0;
}
BZOJ 1799 self 同类分布

 

 7.邱老师选妹子

#include<cstdio>
#include<iostream>
#include<cstring>
#include<cmath>
#include<algorithm>
using namespace std;
int l,r,dp[8][2],ans,t,dig[8],vis[8][2];

int dfs(int len,int sta,bool flag)
{
    if(len==0)return 1;
    if(!flag&&vis[len][sta]) return dp[len][sta];
    int ans=0,up=flag?dig[len]:9;
    for(int i=0;i<=up;i++)
    {
        if(i==4||(sta&&i==2))continue;
        ans+=dfs(len-1,i==6?1:0,flag&&i==up);
    }
    if(!flag){vis[len][sta]=1;dp[len][sta]=ans;}
    return ans;
}

int get(int n)
{
    int t=0;
    while(n)
    {
        dig[++t]=n%10;
        n/=10;
    }
    return dfs(t,0,true);
}

int main()
{
    while(cin>>l>>r)
    {
        if(l==0&&r==0)break;
        cout<<get(r)-get(l-1)<<endl;
    } 
    return 0;
}
邱老师选妹子

 

8.A 题

一道智障级的数位dp

 

中等题

1.bzoj 3530 [Sdoi2014]数数

数位dp+ac自动机

 

2.长沙集训 或

 

较难题

1.luoguP3281 [SCOI2013]数数

 

2.bzoj3598: [Scoi2014]方伯伯的商场之旅

 

转载于:https://www.cnblogs.com/Achenchen/p/7469229.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值