数位dp
数位dp的一般套路
问题形式
给你一个条件
这个问题暴力去做一般是
求解过程
首先我们将区间
每一位作为dp的阶段,设计状态,一般采用记忆化搜索的方法
我们要从高位向低位枚举,因为如果我们确定的高位之后才可以确定低位的范围
比如说一个数
如果我们前两位分别选的是
如果我们前两位分别选的是
也就是我们在记忆化搜索的过程中记一个变量
具体的代码实现看下面的这一道题目
P4127 [AHOI2009]同类分布(经典数位dp)
题目
给出两个数
代码实现如下,细节都在注释里
#include<bits/stdc++.h>
using namespace std;
#define int long long
inline int read()
{
int x=0,f=1;char ch=getchar();
for(;!isdigit(ch);ch=getchar()) if(ch=='-') f=-1;
for(;isdigit(ch);ch=getchar()) x=(x<<1)+(x<<3)+(ch^48);
return x*f;
}
const int N=25,M=210;
int n,a[N],f[N][M][M],mod;
inline int F(int u,int sum,int st,bool lim)
{
if(u>n&&sum==0) return 0;//模数为0是无意义的
if(u>n) return st==0&&sum==mod?1:0;//如果模数恰好为1,那么就是一个合法的解
if(!lim&&f[u][sum][st]!=-1) return f[u][sum][st];//记忆化搜索
int ret=0,res=lim?a[u]:9;//lim判断当前位是否能随便取
for(int i=0;i<=res;++i)
ret+=F(u+1,sum+i,(10*st+i)%mod,i==res&&lim);
return f[u][sum][st]=ret;
}
inline int solve(int x)
{
n=0;
while(x) a[++n]=x%10,x/=10;
reverse(a+1,a+1+n);//从高位到低位枚举
int ret=0;
for(mod=1;mod<=9*n;++mod)//枚举当前模数
{
memset(f,-1,sizeof(f));
ret+=F(1,0,0,1);
}
return ret;
}
int l,r;
signed main()
{
l=read();r=read();
printf("%lld\n",solve(r)-solve(l-1));
return 0;
}
HDU 3709 Balanced Number(普通数位dp)
题目
求区间
平衡数定义:可以通过找一个平衡数位,该数位左边的数位乘以偏移距离的和等于右边的数位乘以偏移距离的和。
比如
题解
显然的是,每个数最多只能有一个平衡数位(毕竟没有物体是有两个重心的)
那么我们仿照上一题枚举模数的做法,这一题我们枚举平衡数位
假设我们将一个数的各位存到
当
通过上面的方式统计出答案即可
HDU 4507 吉哥系列故事——恨7不成妻(平方和的处理)
题目
如果一个数跟
- 整数中的某一位是
- 整数的每一位上的数加起来是
的倍数 - 整数是
的倍数
询问
题解
首先可以通过所有的数的平方和减去与
我们的状态按照下面的思路定义
- 整数中的某一位是
一维状态 表示是否有 出现 - 整数的每一位上的数加起来是
的倍数 一维状态 表示各数位的和模 的值 - 整数是
的倍数 一维状态 表示当前这个数模 的值
于是我们就设出了状态
如果是要求个数,那么这个问题与上面两个问题是没有区别的,可是这里是要我们求平方和
所以考虑一个我们在期望dp那里也用过的套路,为了转移,我们需要维护
表示与 有关的数的个数 表示与 有关的数的和 表示与 有关的数的平方和
考虑转移,假设我们记忆化搜索回溯上来的数为
它的平方对答案的贡献为
假设我们当前状态是
这样就做完了
CF55D Beautiful numbers (数位dp的状态剪枝)
题目
Volodya 认为一个数字
你需要帮助他算出在区间
保证
题解
首先,一个数能够被它的各个数位同时整除,等价于它能被各个数位的
而
因此各个数位的
于是我们设
由于中途的
最后检查
了吗?
我们注意到
注意到
CF908G New Year and Original Order (经典数位dp)
题目
给
题解
首先直接做肯定是没有前途的,冷静一下,我们发现一个性质,这里以
于是我们发现,一个数各数位排序之后会形成一个单调不降的序列,可以将其拆分为
所以我们可以对于每个
ZR2023 NOIP赛前20连测 Day6 T3 (反套路数位dp)
题意
你当前想要做一个有关概率的问题。
具体的,有
当前你需要计算有多少概率使得变量的和小于等于为
当然你并不满足于此,你尝试将这
要求这个十进制数大小
你需要对
为了方便,你只需要输出满足条件的概率乘上
由于答案可能很大,你只需要输出答案在
对于所有测试点满足
题解
首先,你仔细想一下我们上面提到的常规数位dp做法,会发现它很困难,出题人的意思是可以用多项式做,挺抽象的
所以我们考虑另一种做法,我们将所有的
考虑如何求出
注意到一个位置的贡献最高是
然后由于
总时间复杂度即为
带注释代码:
#include<bits/stdc++.h>
using namespace std;
#define int long long
inline int read()
{
int x=0,f=1;char ch=getchar();
for(;!isdigit(ch);ch=getchar()) if(ch=='-') f=-1;
for(;isdigit(ch);ch=getchar()) x=(x<<1)+(x<<3)+(ch^48);
return x*f;
}
const int mod=998244353,M=1010,P=51;
int up[M],dw[M],fac[M],ifac[M];
int n,p,m,N;
inline int ksm(int a,int b)
{
int val=1;
while(b)
{
if(b&1) val=val*a%mod;
a=a*a%mod;
b>>=1;
}
return val;
}
inline void init(int n)
{
N=n;
dw[0]=1;
for(int i=1;i<M;++i) dw[i]=dw[i-1]*(n-i+1)%mod;
up[0]=1;
for(int i=1;i<M;++i) up[i]=up[i-1]*(n+i-1)%mod;
}
inline int Cd(int x)
{
if(x>N) return 0;
return dw[x]*ifac[x]%mod;
}
inline int Cu(int x)
{
return up[x]*ifac[x]%mod;
}
int ok[M],id[M],val[M],cnt[M];
int g[P][M];
int f[P][M][P];
signed main()
{
n=read();p=read();m=read();
fac[0]=1;
for(int i=1;i<M;++i) fac[i]=fac[i-1]*i%mod;
ifac[M-1]=ksm(fac[M-1],mod-2);
for(int i=M-2;i>=0;--i) ifac[i]=ifac[i+1]*(i+1)%mod;
int u=1,tot=0,L=0,R=-1;
for(int i=1;i<=n;++i)//找模p意义下的循环节,用来计算各个剩余类的大小
{
u%=p;
if(ok[u])
{
L=id[u];R=i-1;
break;
}
++cnt[u];
id[u]=++tot;
val[tot]=u;
ok[u]=1;
u*=10;u%=p;
}
if(R!=-1)
{
int k=(n-R)/(R-L+1);
for(int i=L;i<=R;++i) cnt[val[i]]+=k;
u=val[R]*10%p;
for(int i=R+k*(R-L+1)+1;i<=n;++i)
{
u%=p;
++cnt[u];
id[u]=++tot;
val[tot]=u;
ok[u]=1;
u*=10;u%=p;
}
}
for(int i=0;i<p;++i)
{
init(cnt[i]);
for(int j=0;j<=m;++j)
{
int res=0;
for(int k=0;;++k)
{
if(j<10*k) break;
if(k&1) res+=(mod-1)*Cd(k)%mod*Cu(j-10*k)%mod;
else res+=Cd(k)*Cu(j-10*k)%mod;
}
g[i][j]=res%mod;//第 i 个剩余类,给数位和贡献 j 的方案数
}
}
f[0][0][0]=1;//考虑到第 i 个剩余类 ,数位和为 j ,取模结果为 k 时的方案数
for(int i=0;i<p;++i)
for(int j=0;j<=m;++j)
for(int k=0;k<p;++k)
{
if(!f[i][j][k]) continue;
for(int d=0;d<=min(m-j,cnt[i]*9);++d)// 当前考虑贡献 d 到数位和
(f[i+1][j+d][(k+d*i)%p]+=f[i][j][k]*g[i][d])%=mod;
}
for(int i=1;i<=m;++i) (f[p][i][0]+=f[p][i-1][0])%=mod;
for(int i=0;i<=m;++i) cout<<f[p][i][0]<<' ';
return 0;
}
posted on 2023-10-27 18:03 star_road_xyz 阅读(63) 评论(0) 编辑 收藏 举报
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】