20250922 - 数位dp 总结
前言
我们可爱的人机测试,在规定的时间内没有一个人做出来(虽说只有10秒)!!!
被卡成安卓了!!!
数位 dp
众所不周知,周知不众所,dp 是一种比 dfs 要快的方法(记忆化当我没说),所以,在大规模的数据下,dp 的作用非常大。
看名字就知道,数位 dp,就是在数上做 dp,把每一位数字拆开,算出贡献再合并结果!
怎么做呢?请听下回分解!
[ZJOI2010] 数字计数
此题一看,暴力,一看数据范围,呜呜呜,$a,b \le 10^{12} $。
暴力肯定不行了,怎么办?
考虑 dp[limit][l0][k] 为选了 k 个数,上限是 limit ,是否有前导零,那么,就可以愉快的记忆化搜索了!!!
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define ull unsigned ll
#define int ll // 我可"没有" #define int long long
#define db double
const int INF = 0x3f3f3f3f;
const int N = 13 + 7;
const int mod = 1e9 + 7;
ll dp[2][2][N];
ll power[N],suf[N],c[N];
inline ll dfs(const int &now,bool limit,bool l0,int k){
if(k <= 0)
return 0;
if(dp[limit][l0][k] >= 0)
return dp[limit][l0][k];
dp[limit][l0][k] = 0;
int l = limit ? c[k] : 9; // 判断上界
for(int i = 0;i <= l;i++){
dp[limit][l0][k] += dfs(now,limit && c[k] == i,l0 && i == 0,k-1);
if(l0 && i == 0LL) // 如果有前导零,就进行下一次循环
continue;
if(i == now){
if(limit && c[k] == i){
dp[limit][l0][k] += suf[k-1] + 1;
}else{
dp[limit][l0][k] += power[k-1];
}
}
}
return dp[limit][l0][k];
}
inline int query(int now,const int &pos){
memset(dp,-1,sizeof(dp)); // 每次一定要清空!!!
int len = 0;
while(now){
c[++len] = now % 10;
now /= 10;
int i = len;
suf[i] = suf[i-1] + power[len-1] * c[len]; // 求后缀
}
return dfs(pos,1,1,len);
}
void init(){
power[0] = 1;
for(int i = 1;i <= 13;i++){
power[i] = power[i-1] * 10LL;
}
// 计算 10^k 是多少
// 可以用快速幂,但cmy因此被抬走了!
}
ll a,b;
signed main(){
init();
scanf("%lld%lld",&a,&b);
for(int i = 0;i <= 9;i++){
printf("%lld ",query(b,i) - query(a-1,i)); // 前缀和!!!
}
return 0;
}

浙公网安备 33010602011771号