一本通数位DP小记
数位DP
「一本通 5.3 例 1」Amount of Degrees
先将这个数化为 \(B\) 进制
从高位向低位枚举: 设当前为第 i 位
这一位贡献为 从高位到第 i+1 位 都是原来的书的前若干位, 第 i 位小于原数的第 i 位且满足题目的条件, 低位到第 i-1 位 任意
(最后一位的贡献需要单独考虑)
第 i 位:如果填0则答案为 \(C_{i=剩下的个数}^{剩下的1}\)
如果点1则答案为 \(C_{i}^{剩下的1的个数-1}\)
注意一些边界情况等
点击查看代码
// 先转化为前缀和
// 从头到尾枚举每一位: 这一位的贡献为前面的位都相同, 这一位比原来更小, 后面的位任意的方案数
#include <vector>
#include <stdio.h>
#include <string.h>
int k, b;
int C[35][35]; // 组合数
int dp(int n) {
if(!n) return 0;
std::vector<int> num;
while(n) num.push_back(n % b), n /= b;
n = num.size();
int res = 0, last = 0; // res 为答案, last 为当前 1 的个数
for(int i = n - 1; i >= 0; i --) {
int x = num[i];
if(x) { // 这一位非0: 可以选择
res += C[i][k - last]; // 这一位填0的情况(假设前面若干位都是)
if(x > 1) { // 这一位可以尝试填1
if(k - last - 1 >= 0) res += C[i][k - last - 1];
break; // 由于只能填0或1,则
} else // 只能选0: 如果1过多,跳过
if(++ last > k) break;
}
if(!i && last == k) res ++; // 最后一位的贡献
}
return res;
}
int main() {
int l, r; scanf("%d%d%d%d", &l, &r, &k, &b);
for(int i = 0; i <= 34; i ++) {
C[i][0] = 1;
for(int j = 1; j <= i; j ++)
C[i][j] = C[i-1][j] + C[i-1][j-1];
}
printf("%d\n", dp(r) - dp(l - 1));
return 0;
}
类似之前的问题计算贡献(从高向低)
设 f[i][j]
表示共有 i 位, 最高位为 j 的答案
枚举位来统计
点击查看代码
#include <vector>
#include <stdio.h>
#include <string.h>
int f[15][10]; // 共有 i 位, 最高位为 j 的答案
int dp(int n) {
if(!n) return 1; // 0满足条件
std::vector<int> num;
while(n) num.push_back(n % 10), n /= 10;
int res = 0, last = 0; // last 为上一位
for(int i = num.size() - 1; i >= 0; i --) {
int x = num[i];
for(int j = last; j < x; j ++) res += f[i+1][j]; // 第i位填 j, 后面的随便填
if(x < last) break; // 不能够保留
last = x; if(!i) res ++; // 0满足条件
}
return res;
}
int main() {
for(int i = 0; i < 10; i ++) f[1][i] = 1;
for(int i = 2; i < 15; i ++)
for(int j = 0; j < 10; j ++)
for(int k = j; k < 10; k ++) f[i][j] += f[i-1][k];
int l, r; while(scanf("%d%d", &l, &r) == 2) printf("%d\n", dp(r) - dp(l-1));
return 0;
}
类似的,设 f[i][j]
表示总共 i 位数, 首位为 j 的方案数
点击查看代码
#include <math.h>
#include <vector>
#include <stdio.h>
#include <string.h>
int f[15][10]; // 总共 i 位数, 首位为 j 的方案数
int dp(int n) {
if(!n) return 0; // 0不算
std::vector<int> num;
while(n) num.push_back(n % 10), n /= 10;
int res = 0, last = -2;
for(int i = num.size() - 1; i >= 0; i --) {
int x = num[i];
for(int j = (i == num.size() - 1); j < x; j ++) // 首位不能是0
if(abs(j - last) >= 2) res += f[i+1][j]; // 可以选的位
if(abs(x - last) >= 2) last = x;
else break; // 填不下去了
if(!i) res ++; // 0结尾
}
for(int i = 1; i < (int)num.size(); i ++)
for(int j = 1; j < 10; j ++) res += f[i][j]; // 位数不足的
return res;
}
int main() {
for(int i = 0; i < 10; i ++) f[1][i] = 1;
for(int i = 2; i < 15; i ++)
for(int j = 0; j < 10; j ++)
for(int k = 0; k < 10; k ++)
if(abs(j - k) >= 2) f[i][j] += f[i-1][k];
int l, r; scanf("%d%d", &l, &r);
printf("%d\n", dp(r) - dp(l - 1));
return 0;
}
同理,设 f[i][j][k]
总共有 i 位, 最高位为 j, 数字和 mod n 为 k 的方案数
点击查看代码
#include <vector>
#include <stdio.h>
#include <string.h>
int f[15][10][100]; // 总共有 i 位, 最高位为 j, 数字和 mod n 为 k 的方案数
int l, r, mod;
int dp(int n) {
if(!n) return 1;
std::vector<int> num;
while(n) num.push_back(n % 10), n /= 10;
int res = 0, sum = 0; // sum 为前面若干数的和
for(int i = num.size() - 1; i >= 0; i --) {
int x = num[i];
for(int j = 0; j < x; j ++)
res += f[i+1][j][(mod - sum) % mod];
(sum += x) %= mod;
if(!i && sum == 0) res ++;
}
return res;
}
int main() {
while(scanf("%d%d%d", &l, &r, &mod) == 3) {
memset(f, 0, sizeof(f));
for(int i = 0; i < 10; i ++) f[1][i][i % mod] = 1;
for(int i = 2; i < 15; i ++)
for(int j = 0; j < 10; j ++)
for(int k = 0; k < mod; k ++)
for(int t = 0; t < 10; t ++)
f[i][j][k] += f[i-1][t][((k - j) % mod + mod) % mod];
printf("%d\n", dp(r) - dp(l - 1));
}
return 0;
}
点击查看代码
#include <vector>
#include <stdio.h>
#include <string.h>
int f[10][10]; // 有 i 位数, 最高位是 j 的方案数
int dp(int n) {
if (!n)
return 1;
std::vector<int> num;
while (n)
num.push_back(n % 10), n /= 10;
int last = 0, res = 0;
for (int i = num.size() - 1; i >= 0; i --) {
int x = num[i];
for (int j = 0; j < x; j ++)
if (j != 4 && (last != 6 || j != 2))
res += f[i + 1][j];
if (x == 4 || (last == 6 && x == 2))
break;
if (!i)
res ++;
last = x;
}
return res;
}
int main() {
for (int i = 0; i < 10; i ++)
f[1][i] = i != 4;
for (int i = 2; i < 10; i ++)
for (int j = 0; j < 10; j ++)
if (j != 4)
for (int k = 0; k < 10; k ++)
if (j != 6 || k != 2)
f[i][j] += f[i - 1][k];
int l, r;
while (scanf("%d%d", &l, &r) == 2 && (l || r))
printf("%d\n", dp(r) - dp(l - 1));
return 0;
}
恨 7 不成妻
(暂缺)
点击查看代码
#include <vector>
#include <stdio.h>
#include <string.h>
typedef long long LL;
LL calc(const std::vector<int> &vec, int l, int r) {
LL res = 0;
for (int i = r; i >= l; i --)
res = res * 10 + vec[i];
return res;
}
LL pow10[19], l, r;
LL dp(LL n, int x) {
if (!n)
return 0;
std::vector<int> num;
while (n)
num.push_back(n % 10), n /= 10;
n = num.size();
LL res = 0;
// 求出 x 在每一位上出现的次数
for (int i = n - 1 - !x; i >= 0; i --) { // 最高位不能是 0
if (i < n - 1) {
res += calc(num, i + 1, n - 1) * pow10[i]; // 前面若干位 <= n的前面若干位的情况
if (!x)
res -= pow10[i]; // 最高若干位不能全为 0
}
if (num[i] == x)
res += calc(num, 0, i - 1) + 1; // 前面若干位 = n的前面若干位的情况
else if (num[i] > x)
res += pow10[i];
}
return res;
}
int main() {
for (int i = *pow10 = 1; i <= 18; i ++)
pow10[i] = pow10[i - 1] * 10;
scanf("%lld%lld", &l, &r);
for (int x = 0; x < 10; x ++)
printf("%lld ", dp(r, x) - dp(l - 1, x));
return 0;
}