数位dp从入门到入土

最近刚学习了数位 dp ,做了一两题难度觉得不是很大。

一般形式

数位 dp 的一般形式就是在 \([l,r]\) 这个区间要求满足某个要求的数一共有多少个。或是通过某种变化某种 trick 可以变化到这个形式的题目。

而要求 \([l,r]\) 区间满足某个要求则是通过生成的方式求出的。这个问题一般可以 dp 。

这样形式化的说还是比较抽象的,下面给出几道例题来看一下。

例题

P2657 Windy数

题意

求在 \([l,r]\) 之间满足每两位之间相差不小于 \(2\) 的数的个数。例如 \(2\),\(50\),\(520\) 都是 Windy 数。

思路

首先一步转化,把 \([l,r]\) 转化成 \([1,r]-[1,l-1]\)

这道题并不需要通过某种变化和 trick 变换到一般形式,那就只有 dp 的难度了。

考虑暴力生成这个数,然后记忆化搜索即可。

代码

#include <bits/stdc++.h>
#define debug puts("I love Newhanser forever!!!!!");
#define pb push_back
using namespace std;
template <typename T>inline void read(T& t){
    t=0; register char ch=getchar(); register int fflag=1;
    while(!('0'<=ch&&ch<='9')){if(ch=='-') fflag=-1;ch=getchar();}
    while(('0'<=ch&&ch<='9')){t=t*10+ch-'0'; ch=getchar();} t*=fflag;
}
template <typename T,typename... Args> inline void read(T& t, Args&... args){read(t);read(args...);}
const int MAXN=800;
int L,R;
int l[35],cnt1,r[35],cnt2;
int dp[35][10],f[35][10];
int f1(int pos,int pre,bool flag,bool book){
    if(pos>=cnt1) return 1;
    if(!book&&!flag&&dp[pos][pre]!=-1) return dp[pos][pre];
    int max_num=9,res=0;
    if(flag) max_num=l[pos+1];
    for(int i=0;i<=max_num;++i)
    if(abs(i-pre)>=2||book) res+=f1(pos+1,i,flag&(i==l[pos+1]),book&(i==0));
    if(!flag&&!book) dp[pos][pre]=res;
    return res;
}
int f2(int pos,int pre,bool flag,bool book){
    if(pos>=cnt2) return 1;
    if(!book&&!flag&&f[pos][pre]!=-1) return f[pos][pre];
    int max_num=9,res=0;
    if(flag) max_num=r[pos+1];
    for(int i=0;i<=max_num;++i)
    if(abs(i-pre)>=2||book) res+=f2(pos+1,i,flag&(i==r[pos+1]),book&(i==0));
    if(!flag&&!book) f[pos][pre]=res;
    return res;
}
int main(){
    read(L,R);
    int LL=L-1,RR=R;
    memset(dp,-1,sizeof(dp));
    memset(f,-1,sizeof(f));
    if(!LL) l[++cnt1]=0;
    while(LL) l[++cnt1]=LL%10,LL/=10;
    reverse(l+1,l+cnt1+1);
    if(!RR) r[++cnt2]=0;
    while(RR) r[++cnt2]=RR%10,RR/=10;
    reverse(r+1,r+cnt2+1);
    cout<<f2(0,0,1,1)-f1(0,0,1,1)<<endl;
    return 0;
}
//Welcome back,Chtholly.

P4317 花神的数论题

题意

\([1,n]\) 中所有数二级制下 \(1\) 的个数的积。

做时思路

这个题要稍微转换一下。有一个显然的一点,二进制下 \(1\) 的个数十分有限。那么我们直接枚举有多少个 \(1\) 然后回头构造就 ok 了。

构造跟上面一题十分相似,只需要把十进制换成二进制就行了。

这里需要注意的一点是。

\(x^k \equiv x^{k\%p} (mod\ p),p\notin Prime\)

但是快速幂算的东西数量不可能超过 long long ,在 dfs 时不取模也是没事的。

代码

#include <bits/stdc++.h>
#define debug puts("I love Newhanser forever!!!!!");
#define pb push_back
#define int long long
using namespace std;
template <typename T>inline void read(T& t){
    t=0; register char ch=getchar(); register int fflag=1;
    while(!('0'<=ch&&ch<='9')){if(ch=='-') fflag=-1;ch=getchar();}
    while(('0'<=ch&&ch<='9')){t=t*10+ch-'0'; ch=getchar();} t*=fflag;
}
template <typename T,typename... Args> inline void read(T& t, Args&... args){read(t);read(args...);}
const int P=10000007,MAXN=800;
int n,ans,K,i;
int dp[MAXN][MAXN];
int N[MAXN];
int l2(int k){
    int res=0;
    while(k) N[++res]=k%2,k/=2;
    reverse(N+1,N+res+1);
    return res;
}
int quick_pow(int x,int y){
    int res=1;
    while(y){
        if(y&1) res=(1ll*res*x)%P;
        x=(1ll*x*x)%P;
        y>>=1;
    }
    return res;
}
int dfs(int pos,int k,bool flag){
    //现在要填 i 个 1 ,并且一共有 K 位
    if(k>i) return 0;
    if(pos>K) if(k==i) return 1;
    else return 0;
    if(!flag&&dp[pos][k]!=-1) return dp[pos][k];
    if(flag&&N[pos]==0)return dfs(pos+1,k,flag);
    int res=0;
    res+=dfs(pos+1,k+1,flag&(N[pos]==1));
    res+=dfs(pos+1,k,0);
    if(!flag) dp[pos][k]=res;
    return res;
}
signed main(){
    read(n);
    K=l2(n);ans=1;
    //for(int i=1;i<=K;++i) cout<<N[i]<<endl;
    for(i=1;i<=K;++i){
        memset(dp,-1,sizeof(dp));
        //cout<<i<<' '<<dfs(1,0,1)<<endl;
        ans=(1ll*ans*quick_pow(i,dfs(1,0,1)))%P;
    }
    cout<<ans<<endl;
    return 0;
}
//Welcome back,Chtholly.
posted @ 2022-07-02 10:38  Mercury_City  阅读(43)  评论(0编辑  收藏  举报