数位DP题目瞎做
普通的数位DP,题目形式大概是——
在区间
里,统计满足条件的数的个数,并且暴力统计会严重超时
这样就提醒我们在每一位去枚举这一位数字的决策,递推统计方案。对于区间
在解决这个问题的时候,会将数
对于数的第
一般来说,数位DP有两种写法,递推预处理法和记忆化搜索法。前者可以认为是自低位向高位的顺推,后者可以认为是自高位向低位的逆推。
顺推法会提前处理好所有情况下的dp状态值,接下来根据查询的数,在左子树里面按需取值。更适合处理低位决策会影响高位决策的情况,例如状态中会记录低位产生的进位。
逆推法更适合处理高位会影响低位决策的情况,例如状态中会出现数字的大小关系(高位如果小了,低位不会改变大小关系)。整个记忆化搜索的过程就像上图这棵树的过程一样,非常直观。
一般记忆化搜索的模板如下:
点击查看代码
ll solve(int n, state..., bool zero, bool limit, const std::vector<int> &digit)
{
if (n == 0 && 满足条件)
return 1;
if (n == 0)
return 0;
if (!zero && !limit && dp[n][state] != -1)
return dp[n][state];
ll res = 0;
int mx = limit ? digit[n - 1] : 9;
for (int i = 0; i <= mx; ++i)
res += solve(n - 1, newstate, zero&&(i==0), limit & (i == digit[n - 1]), digit);
if (!zero && !limit)
dp[n][state] = res;
return res;
}
zero表示当前是不是前导0,limit表示当前是不是在树的最右端的链上,如果在最右端的链上,那么下一位的取值就不能超过
具体的结合题目来分析。
题目
LightOJ-1068-Investigation
给出区间
,和一个数 ,统计区间中能被 整除且数位之和也能被 整除的数。
首先这个K是唬人的,因为数位之和不超过90。。。那么就可以把余数计入状态,
点击查看代码
void init()
{
for (int i = 1; i <= MAXN; ++i)
for (int j = 0; j < 10; ++j)
for (int x = 0; x < K; ++x)
for (int y = 0; y < K; ++y)
dp[i][j][x][y] = 0;
for (int i = 0; i < 10; ++i)
dp[1][i][i % K][i % K] = 1;
for (int i = 2, v = 1; i <= MAXN; ++i)
{
v = (v * 10) % K;
for (int j = 0; j < 10; ++j)
{
for (int k = 0; k < 10; ++k)
for (int x = 0; x < K; ++x)
for (int y = 0; y < K; ++y)
{
dp[i][j][(x + v * j) % K][(y + j) % K] += dp[i - 1][k][x][y];
}
}
}
}
int solve(int n)
{
if (n == 0)
return 1;
std::vector<int> digit;
for (int x = n; x; x /= 10)
digit.push_back(x % 10);
int res = 0;
int v = 1;
for (int i = 1; i < digit.size(); ++i)
v *= 10;
for (int i = digit.size() - 1; i >= 1; --i)
for (int j = 1; j < 10; ++j)
res += dp[i][j][0][0];
res++;
int m[2] = {0, 0};
for (int i = digit.size() - 1; i >= 0; --i, v /= 10)
{
// printf("%d\n", v);
for (int j = (i == digit.size() - 1); j < digit[i]; ++j)
res += dp[i + 1][j][(K - m[0]) % K][(K - m[1]) % K];
m[0] = (m[0] + 1ll * digit[i] * v % K) % K;
m[1] = (m[1] + digit[i]) % K;
}
if (m[0] == 0 && m[1] == 0)
res++;
return res;
}
Logan and DIGIT IMMUNE numbers
在区间
里面找到第 个“仅由奇数组成,且不能被任意一位数整除的”数。
这里要求第
注意到问题要求不能被任意一位数整除,那么还得记录一下当前出现过哪些数,以及它们对应的余数。一共有
点击查看代码
void init()
{
dp[0][0][0][0][0][0] = 1;
dp[1][1][0][3 % 5][3 % 7][3 % 9] = 1;
dp[1][2][5 % 3][0][5 % 7][5 % 9] = 1;
dp[1][4][7 % 3][7 % 5][0][7 % 9] = 1;
dp[1][8][9 % 3][9 % 5][9 % 7][0] = 1;
ll v = 1;
/*for (int i = 2; i <= 2; ++i)
{
v = 10;
for (int mask = 5; mask < 6; ++mask)
{
for (int a = 0; a <= 0; ++a)
for (int b = 3; b <= 3; ++b)
for (int c = 3; c <= 3; ++c)
for (int d = 3; d <= 3; ++d)
if (mask & 1)
dp[i][mask][(v * 7 + a) % 3][(v * 7 + b) % 5][(v * 7 + c) % 7][(v * 7 + d) % 9] = dp[i - 1][mask][a][b][c][d] + dp[i - 1][mask ^ 4][a][b][c][d];
}
}*/
v = 1;
for (int i = 2; i <= MAXN - 1; ++i)
{
v *= 10;
for (int mask = 1; mask < 16; ++mask)
{
for (int a = 0; a < 3; ++a)
for (int b = 0; b < 5; ++b)
for (int c = 0; c < 7; ++c)
for (int d = 0; d < 9; ++d)
{
if (mask & 1)
dp[i][mask][a][(v * 3 + b) % 5][(v * 3 + c) % 7][(v * 3 + d) % 9] += dp[i - 1][mask][a][b][c][d] + dp[i - 1][mask ^ 1][a][b][c][d];
if (mask & 2)
dp[i][mask][(v * 5 + a) % 3][b][(v * 5 + c) % 7][(v * 5 + d) % 9] += dp[i - 1][mask][a][b][c][d] + dp[i - 1][mask ^ 2][a][b][c][d];
if (mask & 4)
dp[i][mask][(v * 7 + a) % 3][(v * 7 + b) % 5][c][(v * 7 + d) % 9] += dp[i - 1][mask][a][b][c][d] + dp[i - 1][mask ^ 4][a][b][c][d];
if (mask & 8)
dp[i][mask][(v * 9 + a) % 3][(v * 9 + b) % 5][(v * 9 + c) % 7][d] += dp[i - 1][mask][a][b][c][d] + dp[i - 1][mask ^ 8][a][b][c][d];
}
}
}
}
ll solve(ll n)
{
if (n < 10)
return 0;
std::vector<int> digit;
for (ll x = n; x; x /= 10)
digit.push_back(x % 10);
ll res = 0;
ll v = 1;
for (int i = digit.size() - 1; i >= 1; --i)
{
v *= 10;
for (int mask = 1; mask < 16; ++mask)
{
for (int a = (mask & 1) == 1; a < 3; ++a)
for (int b = (mask & 2) == 2; b < 5; ++b)
for (int c = (mask & 4) == 4; c < 7; ++c)
for (int d = (mask & 8) == 8; d < 9; ++d)
res += dp[i][mask][a][b][c][d];
}
}
int last = 0;
ll num = 0;
int d[4] = {3, 5, 7, 9};
for (int i = digit.size() - 1; i >= 0; --i)
{
// printf("%lld\n", res);
for (int k = 0; k < 4; ++k)
{
if (d[k] >= digit[i])
break;
ll tmp = num + 1ll * d[k] * v;
int s0 = last | (1 << k);
for (int s = (i != 0); s < 16; ++s)
{
int mask = s | s0;
for (int a = (mask & 1) == 1; a < 3; ++a)
for (int b = (mask & 2) == 2; b < 5; ++b)
for (int c = (mask & 4) == 4; c < 7; ++c)
for (int d = (mask & 8) == 8; d < 9; ++d)
{
int na = (a + 3 - (tmp % 3)) % 3, nb = (b + 5 - (tmp % 5)) % 5;
int nc = (c + 7 - (tmp % 7)) % 7, nd = (d + 9 - (tmp % 9)) % 9;
res += dp[i][s][na][nb][nc][nd];
}
}
}
if (digit[i] != 3 && digit[i] != 7 && digit[i] != 9 && digit[i] != 5)
return res;
if (digit[i] == 3)
last |= 1;
if (digit[i] == 5)
last |= 2;
if (digit[i] == 7)
last |= 4;
if (digit[i] == 9)
last |= 8;
num += 1ll * digit[i] * v;
v /= 10;
}
// printf("solve %lld = %lld\n", n, res);
for (int k = 0; k < 4; ++k)
{
if (last & (1 << k))
if (num % d[k] == 0)
return res;
}
res++;
return res;
}
void Main()
{
ll l, r, k;
read(l, r, k);
ll base = solve(l - 1);
k += base;
ll mx = solve(r);
if (mx < k)
{
// printf("solve(%d) = %lld\n", r, mx);
printf("-1\n");
return;
}
while (l <= r)
{
ll mid = (l + r) >> 1;
ll res = solve(mid);
// printf("solve(%d) = %lld\n", mid, res);
if (res >= k)
r = mid - 1;
else
l = mid + 1;
}
printf("%lld\n", l);
}
Count Pairs: Used
在
中统计数对 的数量。 需要满足 且 , 代表 的数位之和
这道题要统计的是数对,那么每一位既要枚举
进一步地,由于这里只需要求解一次,那么记忆化搜索中的limit也可以加入到dp状态中。
点击查看代码
int solve(int n, int diff, bool less, bool limit)
{
if (n == 0 && diff > MAXM)
return 1;
else if (n == 0)
return 0;
if (dp[n][diff][less][limit] != -1)
return dp[n][diff][less][limit];
int &res = dp[n][diff][less][limit];
res = 0;
for (int y = limit ? digit[n - 1] : 9; y >= 0; --y)
{
for (int x = less ? 9 : y; x >= 0; --x)
{
res += solve(n - 1, diff + (y - x), less | (x < y), limit & (y == digit[n - 1]));
res %= mod;
}
}
return res;
}
P2657 [SCOI2009] windy 数
标准的数位DP例题,状态里记录一下每一位的数是啥,然后转移就行了。代码不放了...
P3413 SAC#1 - 萌数
题目中要求存在长度为2的回文串的数。不如直接求不存在回文串的数,用总量一减就行了。那么第n位的数不能跟第n-1位相同,也不能跟第n-2位相同,这样就确保没有回文串。DP状态里记录一下前两位的数就行了。
P4127 [AHOI2009]同类分布
枚举数字的和sum,问题就变成有多少数字和为sum且能被sum整除的数,变成一个背包问题。
P2602 [ZJOI2010] 数字计数
标准数位DP例题,枚举每一位的决策,将下一位得到的方案数累加在该决策的答案里。
P4317 花神的数论题
把问题转化一下。假设出现了
那么就求
CF55D
Volodya 认为一个数字
是美丽的,当且仅当 并且对于 的每一个非零位上的数 ,都有 。你需要帮助他算出在区间 中有多少个数是美丽的。
转换一下题意就是说,数
点击查看代码
ll solve(int n, int rem, int cur, int LCM, bool zero, bool limit, const std::vector<int> &digit)
{
if (n == 0 && rem == 0 && cur == LCM)
return 1;
if (n == 0)
return 0;
if (!zero && !limit && dp[n][mp[cur]][rem] != -1)
return dp[n][mp[cur]][rem];
ll res = 0;
int mx = limit ? digit[n - 1] : 9;
res += solve(n - 1, rem, cur, LCM, zero, limit && (0 == digit[n - 1]), digit);
for (int i = 1; i <= mx; ++i)
{
if (LCM % i == 0)
res += solve(n - 1, (rem + (pw[n - 1] * i % LCM)) % LCM, std::lcm(cur, i), LCM, false, limit & (i == digit[n - 1]), digit);
}
if (!zero && !limit)
dp[n][mp[cur]][rem] = res;
return res;
}
CF628D
给你
个数 保证 位数相同。问满足以下条件的数 的个数:
的偶数位是 ,奇数位不是 。 (这里定义偶数位为从高位往低位的数的偶数位) 答案对
取模。
以
点击查看代码
int solve(int n, int rem, int dep, bool zero, bool limit, int digit[])
{
if (n == 0 && rem == 0)
return 1;
if (n == 0)
return 0;
if (!limit && !zero && dp[n][rem][dep] != -1)
return dp[n][rem][dep];
int mx = limit ? digit[n - 1] : 9;
int res = 0;
if (dep)
{
for (int i = 0; i <= mx; ++i)
{
if (d == 0)
{
if (!zero && i == d)
continue;
}
else if (d == i)
continue;
int ndep = (zero && (i == 0)) ? 1 : 0;
res += solve(n - 1, (rem + 1ll * i * pw[n - 1] % m) % m, ndep, zero && (i == 0), limit && (i == digit[n - 1]), digit);
res %= mod;
}
}
else
{
if (d <= mx)
res = solve(n - 1, (rem + 1ll * d * pw[n - 1] % m) % m, 1, zero && (d == 0), limit && (d == digit[n - 1]), digit);
}
if (!zero && !limit)
dp[n][rem][dep] = res;
return res;
}
CF1073E
给定
,求 ~ 之间最多不包含超过 个数码的数的和。
求所有数的和不太好计算。不如单独考虑每一位的贡献。假设第n位填i有k个数,它对答案的贡献就是
点击查看代码
std::pair<int, int> solve(int n, int mask, int target, bool zero, bool limit, int digit[])
{
if (n == 0 && mask == target)
return std::make_pair(1, 0);
if (n == 0)
return std::make_pair(0, 0);
if (!zero && !limit && dp[n][mask][0] != -1)
return std::make_pair(dp[n][mask][0], dp[n][mask][1]);
std::pair<int, int> res;
int mx = limit ? digit[n] : 9;
for (int i = 0; i <= mx; ++i)
{
if ((target & (1 << i)) || (zero && (i == 0)))
{
int nmask = mask | (1 << i);
if (i == 0 && zero)
nmask = mask;
std::pair<int, int> ret = solve(n - 1, nmask, target, zero && (i == 0), limit && (i == digit[n]), digit);
res.first = (res.first + ret.first) % mod;
res.second += ((1ll * ret.first * pw[n - 1] % mod) * i % mod + ret.second) % mod;
res.second %= mod;
}
}
if (!limit && !zero)
dp[n][mask][0] = res.first, dp[n][mask][1] = res.second;
return res;
}
CF1710C
给你一个数
,问:有多少对数 满足 。三个数字构成了一个非退化三角形,也就是两条短边之和大于第三边的长度。
设
点击查看代码
int solve(int n, bool alimit, bool blimit, bool climit, bool tag1, bool tag2, bool tag3)
{
if (n == 0 && tag1 && tag2 && tag3)
return 1;
if (n == 0)
return 0;
int mask1 = (alimit << 2) | (blimit << 1) | climit, mask2 = (tag1 << 2) | (tag2 << 1) | tag3;
if (dp[n][mask1][mask2] != -1)
return dp[n][mask1][mask2];
int &res = dp[n][mask1][mask2];
res = 0;
int mxa = alimit ? digit[n] : 1, mxb = blimit ? digit[n] : 1, mxc = climit ? digit[n] : 1;
for (int a = 0; a <= mxa; ++a)
for (int b = 0; b <= mxb; ++b)
for (int c = 0; c <= mxc; ++c)
{
res += solve(n - 1, alimit && (a == digit[n]), blimit && (b == digit[n]), climit && (c == digit[n]),
tag1 | ((a ^ b) + (b ^ c) > (a ^ c)), tag2 | ((a ^ b) + (a ^ c) > (b ^ c)),
tag3 | ((a ^ c) + (b ^ c) > (a ^ b)));
if (res > mod)
res -= mod;
}
return res;
}
CF1036C
定义一个数字是“好数”,当且仅当它的十进制表示下有不超过
个
给定,问有多少个 使得 且 是“好数”
太裸了,注意前导0就行了。
CF1734F
S 是一个 Thue-Morse序列。它是一个由以下方式生成的无限长 01 字符串:
最初,令为 "0"。
随后进行以下操作无穷多次:将与各位取反后的 连接。以前 4 次操作为例:
次数取反后的 操作后得到的
1 01
2 0110
3 01101001
4 0110100110010110
给定 2 个正整数和 ,求 和 有几位不同。
一个很巧妙的题!这个字符串每扩充
因为这里面是比较
设
点击查看代码
void init()
{
memset(dp, 0, sizeof dp);
dp[1][0][0][0] = ((n & 1) == 0);
dp[1][1][0][0] = ((n & 1) == 0);
dp[1][0][1][0] = (n & 1);
dp[1][1][1][0] = (n & 1);
for (int i = 2; i <= 60; ++i)
{
int bit = (n & (1ll << (i - 1))) != 0;
int pbit = (n & (1ll << (i - 2))) != 0;
for (int x = 0; x < 2; ++x)
{
for (int y = 0; y < 2; ++y)
{
for (int p = 0; p < 2; ++p)
{
int pre = y + pbit + p;
int cur = x + bit + (pre > 1);
for (int odd = 0; odd < 2; ++odd)
dp[i][x][odd][pre > 1] += dp[i - 1][y][(cur & 1) ^ x ^ odd][p];
}
}
}
}
}
ll solve(ll m)
{
if (m == 0)
return __builtin_popcountll(0) % 2 != __builtin_popcountll(n) % 2;
std::vector<int> digit;
for (int i = 0; i < 60; ++i)
digit.push_back((m & (1ll << i)) != 0);
ll res = 0;
ll last = 0, pren = 0;
for (int i = 59; i >= 0; --i)
{
int nbit = (n & (1ll << i)) != 0;
if (digit[i] == 1)
{
for (int p = 0; p < 2; ++p)
{
ll pre = last + pren;
if (p + nbit > 1)
pre += (1ll << (i + 1));
bool odd = (__builtin_popcountll(pre) % 2) ^ (__builtin_popcountll(last) % 2);
res += dp[i + 1][0][odd ^ 1][p];
}
last += (1ll << i);
}
if (nbit)
pren += (1ll << i);
}
if (__builtin_popcountll(m) % 2 != __builtin_popcountll(m + n) % 2)
res++;
return res;
}
CF1290F
个向量组成一个凸包,让这些凸包落在 的正方形里,有多少种方案.
要保证是凸包,可以将向量按逆时针排列,这样就会发现,假设向量
首先构成凸包一定要闭合。那么
接下来就是数位DP,按位枚举
如果选择2进制来做数位DP,时间复杂度是
点击查看代码
void Main()
{
int n, m;
read(n, m);
for (int i = 1; i <= n; ++i)
read(x[i], y[i]);
dp[0][0][0][0][0][0][0] = 1;
int s = (1 << n) - 1;
for (int i = 0; i <= s; ++i)
for (int j = 1; j <= n; ++j)
if (i & (1 << (j - 1)))
(x[j] > 0 ? px : nx)[i] += std::abs(x[j]), (y[j] > 0 ? py : ny)[i] += std::abs(y[j]);
for (int i = 0; i <= 30; ++i)
for (int a = 0; a <= px[s]; ++a)
for (int b = 0; b <= py[s]; ++b)
for (int c = 0; c <= nx[s]; ++c)
for (int d = 0; d <= ny[s]; ++d)
for (int fx = 0; fx < 2; ++fx)
for (int fy = 0; fy < 2; ++fy)
{
if (dp[i][a][b][c][d][fx][fy] == 0)
continue;
for (int mask = 0; mask <= s; ++mask)
{
int A = a + px[mask], B = b + py[mask], C = c + nx[mask], D = d + ny[mask];
if ((A & 1) == (C & 1) && (B & 1) == (D & 1))
{
int bit = (m >> i) & 1;
int FX = (A & 1) == bit ? fx : ((A & 1) > bit);
int FY = (B & 1) == bit ? fy : ((B & 1) > bit);
dp[i + 1][A >> 1][B >> 1][C >> 1][D >> 1][FX][FY] += dp[i][a][b][c][d][fx][fy];
}
}
}
modint998244353 ans = dp[31][0][0][0][0][0][0] - 1;
printf("%d\n", ans);
}
CCPC2022广州M
下班前偶然发现的一道题,看了看题然后骑车回家的时候脑子里口胡了一下。
首先式子是一个异或的式子,不难想到按位算贡献。
第
接下来考虑
点击查看代码
void Main()
{
C[0][0] = 1;
for (int i = 1; i <= 20; ++i)
{
C[i][0] = 1;
for (int j = 1; j <= i; ++j)
C[i][j] = C[i - 1][j - 1] + C[i - 1][j];
}
ll n, m, k;
read(n, m, k);
dp[0][0][0] = 1;
for (int i = 0; i <= 50; ++i)
{
int nbit = ((n & (1ll << i)) != 0);
int mbit = ((m & (1ll << i)) != 0);
// printf("bit:%d, n:%d, m:%d\n", i + 1, nbit, mbit);
for (int j = 0; j <= 100; ++j)
for (int x = 0; x <= k; ++x)
{
if (dp[i][j][x] == 0)
continue;
// printf("dp[%d][%d][%d] = %d\n", i, j, x, dp[i][j][x]);
for (int y = 0; y <= k; ++y)
{
int cur = y * (k - y) + j;
if ((cur & 1) != nbit)
continue;
int mxz = std::min(y, x);
for (int z = 0; z <= mxz; ++z)
{
if (mbit == 1)
dp[i + 1][cur >> 1][z] += C[x][z] * C[k - x][y - z] * dp[i][j][x];
else
dp[i + 1][cur >> 1][x + y - z] += C[x][z] * C[k - x][y - z] * dp[i][j][x];
}
}
}
}
printf("%d\n", dp[51][0][0]);
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效