数位DP
感谢大佬的分享
凌乱之风
注意数位DP只与位数有关,当给出的数的范围很大但知道位数的情况下,可以考虑用数位DP
$dfs$写法
int dfs(int pos, int pre, int lead, int limit,其它记录转移状态的参数) {
if (!pos) {
边界条件
}
if (!limit && !lead && dp[pos][pre] != -1) return dp[pos][pre];
int res = 0, up = limit ? a[pos] : 无限制位;
for (int i = 0; i <= up; i ++) {
if (不合法条件) continue;
res += dfs(pos - 1, 未定参数, 未定参数, limit && i == up);
}
return limit ? res : dp[pos][pre] = res;
}
int cal(int x) {
memset(dp, -1, sizeof dp);
len = 0;
while (x) a[++ len] = x % 进制, x /= 进制;
return dfs(len, 未定参数, 1, 1);
}
int main() {
cin >> l >> r;
cout << cal(r) - cal(l - 1) << endl;
}
例题
1.Windy
Windy 定义了一种 Windy 数:不含前导零且相邻两个数字之差至少为 2的正整数被称为 Windy 数。
Windy 想知道,在 A和 B 之间,包括 A和 B,总共有多少个 Windy 数?
#include
using namespace std;
vectorv;
int dp[15][15];
int dfs(int pos,int pre,int lead,int limit)
{
if(pos==-1) return 1;
if(!limit&&!lead&&dp[pos][pre]!=-1) return dp[pos][pre];
int res=0,mx=limit?v[pos]:9;
for(int i=0;i<=mx;i++)
{
if(abs(i-pre)<2) continue;
if(lead&&i==0) res+=dfs(pos-1,pre,1,limit&&i==mx);
else res+=dfs(pos-1,i,0,limit&&i==mx);
}
return limit?res:dp[pos][pre]=res;
}
int cal(int x)
{
//memset(dp,-1,sizeof dp);
v.clear();
while(x)
{
v.push_back(x%10);
x/=10;
}
int n=v.size();
return dfs(n-1,11,1,1);
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
memset(dp,-1,sizeof dp);
int a,b;
cin>>a>>b;
cout<
2.数字游戏
由于科协里最近真的很流行数字游戏。
某人又命名了一种取模数,这种数字必须满足各位数字之和 mod N 为 0。
现在大家又要玩游戏了,指定一个整数闭区间$[a,b]$问这个区间内有多少个取模数。
#include
using namespace std;
int p;
vectorv;
int dp[15][1005];
int dfs(int pos,int sum,int limit){
if(pos==-1) return sum%p==0;
if(!limit&&dp[pos][sum]!=-1)return dp[pos][sum];
int res=0,mx=limit?v[pos]:9;
for(int i=0;i<=mx;i++)
{
res+=dfs(pos-1,sum+i,limit&&i==mx);
}
return limit?res:dp[pos][sum]=res;
}
int cal(int x){
memset(dp,-1,sizeof dp);
//注意初始赋值为-1,因为可能有些状态不符合条件答案为0,就可以忽略,否则可能会T
v.clear();
while(x)
{
v.push_back(x%10);
x/=10;
}
int n=v.size();
return dfs(n-1,0,1);
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
int a,b;
while(cin>>a>>b>>p){
cout<
3. 度的数量
求给定区间 [X,Y] 中满足下列条件的整数个数:这个数恰好等于 K 个互不相等的 B 的整数次幂之和。
#include
#define ll long long
using namespace std;
vectorv;
ll dp[35][35];
ll dfs(int pos,int cnt,int limit,int b,int k)
{
if(pos==-1) return cnt==k;
if(!limit&&dp[pos][cnt]!=-1) return dp[pos][cnt];
ll res=0;
int mx=limit?v[pos]:b-1;
for(int i=0;i<=mx;i++)
{
if((i==1&&cnt==k)||i>1) continue; //i>1保证互不相等
res+=dfs(pos-1,cnt+(i==1),limit&&i==mx,b,k);
}
return limit?res:dp[pos][cnt]=res;
}
ll cal(int x,int b,int k)
{
memset(dp,-1,sizeof dp);
v.clear();
while(x)
{
v.push_back(x%b);
x/=b;
}
int n=v.size();
return dfs(n-1,0,1,b,k);
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
int x,y;
int k,b;
cin>>x>>y>>k>>b;
cout<
4. 恨7不成妻
如果一个整数符合下面三个条件之一,那么我们就说这个整数和 7 有关:
- 整数中某一位是 7;
- 整数的每一位加起来的和是 7 的整数倍;
- 这个整数是 7 的整数倍。
现在问题来了:吉哥想知道在一定区间内和 7 无关的整数的平方和。
#include
using namespace std;
constexpr long long mod = 1e9+7;
using ll=long long;
ll Power[25];
vectorv;
struct dp{
ll cnt,sum,sum2;
}f[20][7][7];
dp dfs(int pos,int sum,int num,int limit){
if(pos==-1) return (dp){sum&&num,0,0};
if(!limit&&f[pos][sum][num].cnt!=-1) return f[pos][sum][num];
int mx=limit?v[pos]:9;
dp ans{0,0,0};
for(int i=0;i<=mx;i++)
{
if(i==7) continue;
dp tmp=dfs(pos-1,(sum+i)%7,(num*10+i)%7,limit&&(i==mx));
ll k=i*Power[pos]%mod;
ans.cnt=(ans.cnt+tmp.cnt)%mod;//tmp.cnt存的是第pos-1位到最后一位满足条件的数的值
ans.sum=((ans.sum+tmp.cnt*k%mod)%mod+tmp.sum)%mod;
/*
比如1222 1333 1444
现在后3位的和已经知道了,那么当前位是1的和就是 1000*3+(222+333+444)
*/
ans.sum2=((ans.sum2+(k*k%mod*tmp.cnt%mod+tmp.sum2)%mod)%mod+tmp.sum*k%mod*2%mod)%mod;
/*
以1222为例,1222^2=1000^2+2*1000*222+222^2
*/
}
return limit?ans:f[pos][sum][num]=ans;
}
ll cal(ll x)
{
v.clear();
while(x)
{
v.push_back(x%10);
x/=10;
}
int n=v.size();
return dfs(n-1,0,0,1).sum2%mod;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
int T;
cin>>T;
Power[0]=1;
for(int i=1;i<=20;i++)
Power[i]=Power[i-1]*10%mod;
memset(f,-1,sizeof f);
while(T--)
{
ll l,r;
cin>>l>>r;
cout<<(cal(r)-cal(l-1)+mod)%mod<<'\n';
}
}
6.同类分布
给出两个数a,b,求出[a,b]中各位数字之和能整除原数的数的个数。$1\le a,b\le 10^{18}$
#include
#define ll long long
using namespace std;
ll dp[20][170][170];
vectorv;
ll dfs(int pos,int sum,int rem,int limit,int mod){
if(sum+9*(pos+1)>l>>r;
cout<
7.花神的数论题
设$sum(i)$表示$i$二进制中$1$的个数,给定$N$,求$\prod_{i=1}^Nsum(i)$
转化为$\prod_{i=1}{bit(N)}i{cnt[i]}$
/*
设dp[i][j][k]为在前i位中,已经有j个1,且该数总计有k个1的数的个数。
还有组合数学的做法。
*/
#include
#define ll long long
using namespace std;
const int mod=10000007;
ll dp[64][64][64];
vectorv;
ll dfs(int pos,int cnt,int num,int limit){
if(pos==-1) return cnt==num;
if(!limit&&dp[pos][cnt][num]!=-1) return dp[pos][cnt][num];
ll res=0;
int mx=limit?v[pos]:1;
for(int i=0;i<=mx;i++){
res+=dfs(pos-1,cnt+(i==1),num,limit&&i==mx);
}
return limit?res:dp[pos][cnt][num]=res;
}
ll qmi(ll x,ll y)
{
ll res=1;
while(y)
{
if(y&1) res=res*x%mod;
x=x*x%mod;
y>>=1;
}
return res;
}
ll cal(ll x){
v.clear();
while(x){
v.push_back(x%2);
x/=2;
}
int n=v.size();
ll res=1;
for(int i=1;i<=n;i++)
{
memset(dp,-1,sizeof dp);
res=res*qmi(i,dfs(n-1,0,i,1))%mod;
}
return res;
}
int main(){
ios::sync_with_stdio(false);
cin.tie(0);
ll n;
cin>>n;
cout<
8.J. Junior Mathematician
定义函数$f(x)=\sum_{i=1}{k-1}\sum_{j=i+1}kd(x,i)\times d(x,j)$,其中$d(x,i)$为$x$在十进制下第$i$位的数字,$k$为$x$在十进制下的位数。
定义一个数是好的数字为$x \equiv f(x) \pmod m$
给定$L$和$R$,求$[L,R]$中好的数的个数
$10\le L\le R\le 10^{5000}$ $2\le m\le 60$
答案对$10^9+7$取模
将$x \equiv f(x) \pmod m$转换为$x-f(x) \equiv 0 \pmod m$
这样记录$x \pmod m$和$f(x) \pmod m$的维数可以合并为1维
作差这个思想很常见
#include
#define ll long long
using namespace std;
const int mod=1e9+7;
int pw[5005]; //这个不要开ll
ll dp[5005][65][65];
int v[5005];
int P;
inline ll dfs(int pos,int sum,int rem,int limit)
{
if(pos==-1) return rem==0;
if(!limit&&dp[pos][sum][rem]!=-1) return dp[pos][sum][rem];
ll res=0;
int mx=limit?v[pos]:9;
for(int i=0;i<=mx;i++)
res+=dfs(pos-1,(sum+i)%P,((rem+pw[pos+1]*i-sum*i)%P+P)%P,limit&&i==mx);
/*
例如:
1234当枚举到第4位时
12340-f(12340)=12340-(f(123)+(1+2+3)*4)=12300-f(123)+40-(1+2+3)*4=rem+i*pw[pow+1]-sum(i)
*/
res%=mod;
return limit?res:dp[pos][sum][rem]=res;
}
inline int cal(string s,bool flag)
{
int n=s.size();
//用memset会T
for(int a=0;a<=n;a++)
for(int b=0;b<=P;b++)
for(int c=0;c<=P;c++) dp[a][b][c]=-1;
for(int i=n-1;i>=0;i--) v[n-1-i]=s[i]-'0';
if(flag)
{
v[0]--;
for(int i=0;i>T;
while(T--){
string l,r;
cin>>l>>r;
cin>>P;
int len=r.size();
pw[1]=1;
for(int i=2;i<=len;i++) pw[i]=pw[i-1]*10%P;
cout<<(cal(r,0)-cal(l,1)+mod)%mod<<'\n';
}
}