ABC154-E Almost Everywhere Zero
この問題を考えます.素直な桁DPです.
問題文
$1$ 以上 $N$ 以下の整数であって、$10$ 進法で表したときに、$0$ でない数字がちょうど $K$ 個あるようなものの個数を求めてください。
制約
$1 \leq N < 10^{100}$
$1 \leq K \leq 3$
この問題について,maspy さんのHP では「メモ化再帰」での実装が解説されています.数え上げる対象を $1$ の位で場合分けして,漸化式を作るものです.
このメモ化再帰を C++ でもやってみます.多倍長整数を使って $\lfloor N/10 \rfloor$ や $\lfloor N/10 \rfloor - 1$ をそのまま持ち回す代わりに,制約を「$X \leq (\text{$N$ の上 $i$ 桁})$」「$X < (\text{$N$ の上 $i$ 桁})$」の形で表現しています.
コメント中の s[..i]
は「入力の上 $i$ 桁」を表します.$X\;(=10q+r)$ は数え上げる対象です.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
char s[101]; // 入力
int memo[101][2][4];
// X <= s[..i] もしくは X < s[..i] に対する数え上げ
int f(int i, bool leq, int k) {
if (k < 0) return 0;
int& ret = memo[i][leq][k];
if (ret != -1) return ret;
if (i == 0) {
// X == 0 のみ
return ret = (leq && k == 0) ? 1 : 0;
}
int ub = s[i - 1] - '0' + leq;
// q := X / 10, r := X % 10
// 10 * q + r < 10 * s[..i-1] + ub
ret = 0;
rep(r, 10) ret += f(i - 1, r < ub, k - (r != 0));
return ret;
}
|
提出
「$1$ の位だけ見たときに不等式制約を満たしている $\Longleftrightarrow$ それより上の桁では等号付きの不等式が成立すれば十分」と意識すると書きやすいです.
このメモ化再帰を,そのままループに書き換えてみます.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
char s[101];
// (i, leq, k): X <= s[..i] もしくは X < s[..i] に対する数え上げ
int dp[101][2][4];
int main() {
int k;
scanf("%s%d", s, &k);
int n = strlen(s);
dp[0][1][0] = 1;
rep(i, n) rep(leq, 2) rep(k, 4) rep(r, 10) {
int ub = s[i] - '0' + leq;
if (k - (r != 0) >= 0)
dp[i + 1][leq][k] += dp[i][r < ub][k - (r != 0)];
}
printf("%d\n", dp[n][1][k]);
}
|
提出
貰うDPであるという点以外はよくある桁DPの実装と同じ形になったと思います.変数 leq
は桁DPのいわゆる tight
フラグと役割が似ています.
ABC129-E Sum Equals Xor
$a + b \leq L$ かつ $a + b = a\ \mathrm{XOR}\ b$ なる非負整数の組 $(a, b)$ の個数を求めよ.
$1 \leq L < 2^{100001}$
最初から貰うDPの形で書いてみます.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
char s[100002];
// (i, leq): a + b <= s[..i] もしくは a + b < s[..i] に対する数え上げ
mint dp[100002][2];
int main() {
scanf("%s", s);
int n = strlen(s);
dp[0][1] = 1;
rep(i, n) rep(leq, 2) {
mint& ans = dp[i + 1][leq];
int ub = s[i] - '0' + leq;
ans += dp[i][0 + 0 < ub];
ans += dp[i][0 + 1 < ub] * 2;
}
printf("%d\n", dp[n][1].val());
}
|
提出
比較のため,配るDPも書いておきます.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
char s[100002];
// (i, tight)
mint dp[100002][2];
int main() {
scanf("%s", s);
int n = strlen(s);
dp[0][1] = 1;
rep(i, n) rep(tight, 2) {
mint prv = dp[i][tight];
if (s[i] == '0') {
// 0, 0
dp[i + 1][tight] += prv;
// 0, 1
if (!tight) dp[i + 1][false] += prv * 2;
} else {
// 0, 0
dp[i + 1][false] += prv;
// 0, 1
dp[i + 1][tight] += prv * 2;
}
}
printf("%d\n", (dp[n][0] + dp[n][1]).val());
}
|
提出
ABC138-F Coincidence (おまけ)
$L \leq x \leq y \leq R$,$y < 2x$,$\operatorname{bits}(x) \subseteq \operatorname{bits}(y)$ なる $x$,$y$ を数える問題です.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
bitset<64> l, r;
// (i, l_x, y_r, y_2x): (x, y) の数え上げ
// l_x: 数え上げ対象の条件が l <= x (1) か l < x (0) か
// y_r: y <= r か y < r か
// y_2x: y <= 2x か y < 2x か
mint dp[65][2][2][2];
int main() {
long long li, ri;
scanf("%lld%lld", &li, &ri);
l = li, r = ri;
dp[0][1][1][1] = 1;
rep(i, 64) rep(l_x, 2) rep(y_r, 2) {
int xlb = l[63 - i] - l_x, yub = r[63 - i] + y_r;
rep(yi, 2) rep(xi, yi + 1) { // y, x の一の位
dp[i + 1][l_x][y_r][0]
+= dp[i][xi > xlb][yi < yub][yi < xi * 2];
dp[i + 1][l_x][y_r][1]
+= dp[i][xi > xlb][yi < yub][yi <= xi * 2];
}
}
printf("%d\n", dp[64][1][1][0].val());
}
|
提出