数位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.