『学习笔记』数位 dp(todo)
对于与大整数数位有关的问题,可以考虑使用数位 dp 解决,将大整数看作一个由 \(0 \sim 9\) 构成的整数序列来 dp。
P2657 [SCOI2009] windy 数
不含前导零且相邻两个数字之差至少为 \(2\) 的正整数为 windy 数。求 \([l,r]\) 中 windy 数个数。\(2 \times 10^9\)。
转化问题,答案即为 \([1,r]\) 中个数减去 \([1,l-1]\) 中个数,于是只需要考虑如何求 \([1,x]\) 间个数。
下面是数位 dp 板子做法。
设 \(dp_{i,j,lim,zero}\) 表示前 \(i\) 高位且第 \(i\) 位为 \(j\),前 \(i\) 高位是否与 \(x\) 一致(\(lim \in \{0,1\}\)),是否全部为 \(0\) 的个数(\(zero \in \{0,1\}\))。
设新枚举的第 \(i+1\) 位为 \(k\),\(x\) 第 \(i\) 位为 \(a_i\)。先考虑几个限制:
- \(lim=1\) 且 \(k>a_{i+1}\):这时若拼接上 \(k\) 则最终的数一定超过 \(x\),“剪枝”?
- \(zero=0\) 且 \(|k-j|<2\):此时已不属于前导零(这是个坑),依据题意,相邻两个数字之差至少为 \(2\)。
转移很好理解。
memset(f,0,sizeof(f));
f[0][0][1][1]=1;
for(int i=0; i<=10; i++) // 前i高位
for(int j=0; j<=9; j++) // 第i位为j
for(int lim=0; lim<=1; lim++) // 是否卡最大
for(int zero=0; zero<=1; zero++) // 是否为前导零
for(int k=0; k<=9; k++){ // 枚举第i+1位为k
if(lim && k>a[i+1]) break;
if(!zero && abs(k-j)<2) continue;
f[i+1][k][lim&&k==a[i+1]][zero&&!k]+=f[i][j][lim][zero];
}
记录详情。
P4124 [CQOI2016] 手机号码
一个手机号码满足:\(11\) 位;不含前导 \(0\);至少出现 \(3\) 个相邻且相同的数字;不同时出现 \(8\) 和 \(4\)。
求 \([l,r]\) 内手机号码个数。\(10^{10} \le l \le r < 10^{11}\)。

浙公网安备 33010602011771号