1. 不要62(数位dp)
#include <cstdio>
#include <cstring>
#include <iostream>
#include <string>
using namespace std;
typedef long long ll;
int a[20];
int dp[20][2];
int dfs(int pos, int pre, int sta, bool limit) {
//limit是表示前一位是否为上限位
//pos表示当前位
//pre表示前一位
//sta表示前一位是否为题目限制敏感数(这题是6)
if (pos == -1)
return 1;
if (!limit && dp[pos][sta] != -1)
return dp[pos][sta];
int up = limit ? a[pos] : 9;
int tmp = 0;
for (int i = 0; i <= up; i++) {
if (pre == 6 && i == 2)
continue;
if (i == 4)
continue; //都是保证枚举合法性
tmp += dfs(pos - 1, i, i == 6, limit && i == a[pos]);
}
if (!limit)
dp[pos][sta] = tmp;
return tmp;
}
int solve(int x) {
int pos = 0;
while (x) {
a[pos++] = x % 10;
x /= 10;
}
return dfs(pos - 1, -1, 0, true);
}
int main() {
int le, ri;
// memset(dp,-1,sizeof dp);可优化
while (~scanf("%d%d", &le, &ri) && le + ri) {
memset(dp, -1, sizeof dp);
printf("%d\n", solve(ri) - solve(le - 1));
}
return 0;
}
2.F(x)(数位dp减法)
#include <cstdio>
#include <cstring>
#include <iostream>
#include <string>
using namespace std;
const int N = 1e4 + 5;
int dp[12][N];
int f(int x) {
if (x == 0)
return 0;
int ans = f(x / 10);
return ans * 2 + (x % 10);
}
int all;
int a[12];
int dfs(int pos, int sum, bool limit) {
if (pos == -1) {
return sum <= all;
}
if (sum > all)
return 0;
if (!limit && dp[pos][all - sum] != -1)
return dp[pos][all - sum];
int up = limit ? a[pos] : 9;
int ans = 0;
for (int i = 0; i <= up; i++) {
ans += dfs(pos - 1, sum + i * (1 << pos), limit && i == a[pos]);
}
if (!limit)
dp[pos][all - sum] = ans;
return ans;
}
int solve(int x) {
int pos = 0;
while (x) {
a[pos++] = x % 10;
x /= 10;
}
return dfs(pos - 1, 0, true);
}
int main() {
int a, ri;
int T_T;
int kase = 1;
scanf("%d", &T_T);
memset(dp, -1, sizeof dp);
while (T_T--) {
scanf("%d%d", &a, &ri);
all = f(a);
printf("Case #%d: %d\n", kase++, solve(ri));
}
return 0;
}
3.Round Numbers
4.Beautiful Numbers
#include <bits/stdc++.h>
using namespace std;
#define ll long long
int t;
int a[20];
ll dp[20][110][110][110];
ll dfs(int pos, int mod, int yu, int sum, bool limit) {
if (sum > mod) {
return 0;
}
if (pos == -1) {
return mod == sum && yu == 0;
}
if (!limit && dp[pos][mod][yu][sum] != -1) {
return dp[pos][mod][yu][sum];
}
int up = limit ? a[pos] : 9;
ll ans = 0;
for (int i = 0; i <= up; ++i) {
ans +=
dfs(pos - 1, mod, (yu * 10 + i) % mod, sum + i, limit && (i == up));
}
if (!limit) {
dp[pos][mod][yu][sum] = ans;
}
return ans;
}
ll solve(ll n) {
int pos = 0;
while (n) {
a[pos++] = n % 10;
n /= 10;
}
ll ans = 0;
for (int i = 1; i <= 108; ++i) {
ans += dfs(pos - 1, i, 0, 0, true);
}
return ans;
}
int main() {
cin >> t;
memset(dp, -1, sizeof(dp));
for (int i = 1; i <= t; ++i) {
ll n;
cin >> n;
cout << "Case " << i << ": " << solve(n) << endl;
}
return 0;
}
题解:数位dp,题解链接。有一步要知道的是(yu * 10 + i) % mod,就是假如1234%3 = ((((1*10+2)%3)10+3)%310+4)%3,它们的结果是一样的
5.明七暗七
#include <bits/stdc++.h>
using namespace std;
#define ll long long
ll n, m;
int a[20];
ll dp[20][20][2];
ll dfs(int pos, int sum, int sta, bool limit) {
if (pos == -1) {
return !sum || sta;
}
if (!limit && dp[pos][sum][sta] != -1) {
return dp[pos][sum][sta];
}
int up = limit ? a[pos] : 9;
ll ans = 0;
for (int i = 0; i <= up; ++i) {
ans += dfs(pos - 1, ((sum * 10) + i) % 7, sta || i == 7,
limit && (i == up));
}
if (!limit) {
dp[pos][sum][sta] = ans;
}
return ans;
}
ll solve(ll x) {
int pos = 0;
while (x) {
a[pos++] = x % 10;
x /= 10;
}
return dfs(pos - 1, 0, 0, true);
}
int main() {
cin >> m >> n;
memset(dp, -1, sizeof(dp));
ll l = m + 1, r = 1e18, mid = l + r >> 1;
ll sub = solve(m);
while (l < r) {
if (solve(mid) - sub >= n)
r = mid;
else
l = mid + 1;
mid = l + r >> 1;
}
printf("%lld\n", mid);
return 0;
}
题解:有点像是“不要62”的板子,但是又有点不一样,不一样在于这里一当有一个7都要记录在sta中,相当于sta || i == 7,因为前面数位一但有了7,就会影响后面所有数位的判断,导致dp数组要即使是相同的pos和sum也要分别对sta=0和sta=1区分。
**
6.好朋友
**
题目链接
#include <bits/stdc++.h>
using namespace std;
#define ll long long
ll l, r, t, k;
int a[20];
ll dp[20][2][2][2][2];
ll dfs(int pos, bool limit, bool iffirst, bool sta1, bool sta2, bool sta3) {
if (pos == -1) {
return sta3;
}
if (!limit && dp[pos][iffirst][sta1][sta2][sta3] != -1) {
return dp[pos][iffirst][sta1][sta2][sta3];
}
int up = limit ? a[pos] : 9;
ll ans = 0;
for (int i = 0; i <= up; ++i) {
ans += dfs(pos - 1, limit && i == up, iffirst && i == 0,
sta1 || (!iffirst && i == 0),
sta2 || (!iffirst && sta1 && i == 0),
sta3 || (!iffirst && sta2 && i == 7));
}
if (!limit) {
dp[pos][iffirst][sta1][sta2][sta3] = ans;
}
return ans;
}
ll solve(ll x) {
int pos = 0;
while (x) {
a[pos++] = x % 10;
x /= 10;
}
return dfs(pos - 1, true, true, false, false, false);
}
int main() {
cin >> t;
memset(dp, -1, sizeof(dp));
for (int i = 0; i < t; ++i) {
scanf("%lld %lld", &l, &r);
k ^= (solve(r) - solve(l - 1));
}
cout << k << endl;
return 0;
}
题解:这类题写多了有点思路了,就是把当前位的所有状态都记录下来,做一个dp数组,但凡少一个状态都可能影响dp数组的计数,例如这题的iffirst判断是否为前导零,sta1判断是否为第一个零,sta2判断是否为第二个零,sta3判断是否为7,于是每个状态都开一维储存,然后按模板搞就行了。
**
7.[AHOI2009]SELF 同类分布
**
题目链接
#include <bits/stdc++.h>
using namespace std;
#define ll long long
ll l, r;
int a[20];
ll dp[20][200][200];
ll dfs(int pos, bool limit, int mod, int sum, int all) {
if (sum > all) {
return 0;
}
if (pos == -1) {
return sum == all && mod == 0;
}
if (!limit && dp[pos][mod][sum] != -1) {
return dp[pos][mod][sum];
}
int up = limit ? a[pos] : 9;
ll ans = 0;
for (int i = 0; i <= up; ++i) {
ans +=
dfs(pos - 1, limit && i == up, (mod * 10 + i) % all, sum + i, all);
}
if (!limit) {
dp[pos][mod][sum] = ans;
}
return ans;
}
ll solve(ll x) {
int pos = 0;
while (x) {
a[pos++] = x % 10;
x /= 10;
}
ll ans = 0;
for (int i = 1; i <= 168; ++i){
memset(dp, -1, sizeof(dp));
ans += dfs(pos - 1, true, 0, 0, i);
}
return ans;
}
int main() {
memset(dp, -1, sizeof(dp));
scanf("%lld %lld", &l, &r);
cout << (solve(r) - solve(l - 1)) << endl;
return 0;
}
题解:因为题目数据超级大,有10^18,所以限制了正常的做法,不能开longlong的太多维数组,不然会爆内存,于是就减少了本应该储存all的这一维,但省略了all这一维之后又产生了新的bug,就是对于不同的all值如果仍然使用相同的dp数组就会导致一些意料以外的冲突,所以每次做完dfs都应该恢复dp数组的默认值-1重新计数。
**
8.[CQOI2013]二进制A+B(难,不是很理解,不知道算不算数位dp)
**
题目链接
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
template <typename T>
inline T min(T& a, T& b) {
return a < b ? a : b;
}
template <typename T>
inline int find(T a) {
int res = 0;
for (; a; a >>= 1)
++res;
return res;
} //计算位数
template <typename T>
inline int cont(T x) {
int res = 0;
for (; x; x &= (x - 1))
++res;
return res;
} //计算二进制下1的个数
ll f[33][33][33][33][2], ans, inf;
int main() {
memset(f, 63, sizeof(f));
inf = ans = f[0][0][0][0][0];
int A, B, C, mxd;
scanf("%d%d%d", &A, &B, &C);
mxd = max(max(find(A), find(B)), find(C));
A = cont(A), B = cont(B), C = cont(C);
f[0][0][0][0][0] = 0;
for (int i = 0; i <= mxd; ++i)
for (int a = 0; a <= min(i + 1, A); ++a)
for (int b = 0; b <= min(i + 1, B); ++b)
for (int c = 0; c <= min(i + 1, C); ++c) {
// 8种枚举,其实都是min左边的状态和右边的状态是一样的数位,
//都是从高位到低位,右边是低位全置零的状态,左边是低位置零或置一的八种状态
//而且每次左边的c+1,右边都要同等地|1,其实就是+1,两者总是同步进行的
//只要a或者b或者是进位上进了一,c就要+1,当a,b,进位三者中有两者同为一,就可以抵消然后进位,c就不用+1了
f[i + 1][a][b][c][0] = min(f[i + 1][a][b][c][0], f[i][a][b][c][0] << 1);
f[i + 1][a + 1][b][c + 1][0] = min(f[i + 1][a + 1][b][c + 1][0], f[i][a][b][c][0] << 1 | 1);
f[i + 1][a][b + 1][c + 1][0] = min(f[i + 1][a][b + 1][c + 1][0], f[i][a][b][c][0] << 1 | 1);
f[i + 1][a + 1][b + 1][c][0] = min(f[i + 1][a + 1][b + 1][c][0], f[i][a][b][c][1] << 1);
f[i + 1][a][b][c + 1][1] = min(f[i + 1][a][b][c + 1][1],f[i][a][b][c][0] << 1 | 1);
f[i + 1][a][b + 1][c][1] =min(f[i + 1][a][b + 1][c][1], f[i][a][b][c][1] << 1);
f[i + 1][a + 1][b][c][1] =min(f[i + 1][a + 1][b][c][1], f[i][a][b][c][1] << 1);
f[i + 1][a + 1][b + 1][c + 1][1] =min(f[i + 1][a + 1][b + 1][c + 1][1],f[i][a][b][c][1] << 1 | 1);
}
for (int i = 0; i <= mxd; ++i)
ans = min(ans, f[i][A][B][C][0]);
if (ans == inf)
printf("-1");
else
printf("%lld", ans);
return 0;
}
题解:挺难理解的,我也不是很理解,代码里面有一点我的注释理解,以后有了新的理解会更新。
**
9.[CQOI2016]手机号码
**
题目链接
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
ll l, r;
int a[20];
ll dp[15][20][20][20][20];
ll dfs(int pos, bool limit, int used, int last, int sum, int maxl, int lead) {
if (pos == -1) {
return maxl >= 3;
}
if (!limit && dp[pos][used][last][sum][maxl] != -1) {
return dp[pos][used][last][sum][maxl];
}
int up = limit ? a[pos] : 9;
ll ans = 0;
for (int i = lead; i <= up; ++i) {
if ((used == 4 && i == 8) || (used == 8 && i == 4)) {
continue;
}
ans += dfs(pos - 1, limit && i == up, i == 8 || i == 4 ? i : used, i,
i == last ? sum + 1 : 1, max(maxl, sum + (i == last)), 0);
}
if (!limit) {
dp[pos][used][last][sum][maxl] = ans;
}
return ans;
}
ll solve(ll n) {
int pos = 0;
while (n) {
a[pos++] = n % 10;
n /= 10;
}
if (pos != 11) {
return 0;
}
return dfs(pos - 1, true, 0, 0, 0, 0, 1);
}
int main() {
memset(dp, -1, sizeof(dp));
cin >> l >> r;
cout << solve(r) - solve(l - 1) << endl;
return 0;
}```
题解:注意题目说电话号码只能是11位,不是11位的直接返回0。lead的作用的可以规避前导零,让dfs从最高位开始的话只能最小是1。其他的老生常谈。
**
## 10.[SCOI2009]WINDY数
**
[题目链接](https://ac.nowcoder.com/acm/problem/20268)
![在这里插入图片描述](https://img-blog.csdnimg.cn/20210506163717442.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2JfYl9sYWlfbGFp,size_16,color_FFFFFF,t_70)
![在这里插入图片描述](https://img-blog.csdnimg.cn/20210506163725908.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2JfYl9sYWlfbGFp,size_16,color_FFFFFF,t_70)
```cpp
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
ll l, r;
int a[20];
ll dp[15][20][2];
ll dfs(int pos, bool limit, int last, bool zero) {
if (pos == -1) {
return 1;
}
if (!limit && dp[pos][last][zero] != -1) {
return dp[pos][last][zero];
}
int up = limit ? a[pos] : 9;
ll ans = 0;
for (int i = 0; i <= up; ++i) {
if (!zero && last != -1 && abs(i - last) < 2) {
continue;
}
ans += dfs(pos - 1, limit && i == up, i, zero && i == 0);
}
if (!limit) {
dp[pos][last][zero] = ans;
}
return ans;
}
ll solve(ll n) {
int pos = 0;
while (n) {
a[pos++] = n % 10;
n /= 10;
}
return dfs(pos - 1, true, -1, true);
}
int main() {
cin >> l >> r;
memset(dp, -1, sizeof(dp));
cout << solve(r) - solve(l - 1) << endl;
return 0;
}
题解:还是老生常谈了,注意区分前导零。