ABC 169 F题 + 171 F题 + 小结
一.前言&小结
自闭了, T 2 T2 T2 好像之前做过,于是直接开始莽,但印象不是特别深刻,所以中途换了很多次思路,很久后发现错了一个很 s b sb sb 东西(把代码改来改去,写的什么都乱了…)。 T 2 T2 T2 卡了 40 m i n + 40min^+ 40min+ 后总算搞出来了,切了 T 3 T3 T3, T 4 T4 T4 后一看 T 6 T6 T6 做过(而且还写了题解的),于是也直接开始莽,然后也因为印象不是特别深刻,思路错了,然后推出来一个 O ( n ⋅ ∣ s ∣ ) O (n \cdot |s|) O(n⋅∣s∣) 的递推转移,一直卡在 T 6 T6 T6,结果 T 5 T5 T5 都没怎么看,直接自闭。
所以:
- 远古之前写的题解要经常翻翻,大多都是我当时不会的题,
说不定我现在也不会。 - 写代码之前思路一定要清晰,不然思路&代码改来改去,最后思路&代码都乱了。
- 不要死磕,时间越长,改动的次数越多,会对自己的代码越来越陌生,
还不如重构一遍。
二.题解
1. One Quadrillion and One Dalmatians
一句话题意: n = p 1 ⋅ 2 6 1 + p 2 ⋅ 2 6 2 + . . . + p k ⋅ 2 6 k ( ∀ p i ≠ 0 ) n = p_1 \cdot 26^{1}+p_2 \cdot 26^{2} +... +p_k \cdot 26^{k}(\forall p_i \neq 0) n=p1⋅261+p2⋅262+...+pk⋅26k(∀pi=0)
最开始我没这样翻译,而是翻译成了 10 10 10 进制和 27 27 27 进制的转化,怎么调都调不过去。
这样翻译了之后,做法应该相当明显,从大到小开始判断,如果 p i = 0 p_i = 0 pi=0,则向高位借,如果高位变为 0 0 0,则继续向高高位借,直到高位不为零或者到了最高位。
#include <cmath>
#include <cstdio>
#include <iostream>
#define LL long long
#define ULL unsigned long long
using namespace std;
const int Maxn = 15;
ULL n;
ULL w[Maxn + 5], k[Maxn + 5];
int main () {
cin >> n;
w[1] = 1;
for (int i = 2; i <= Maxn; i++) {
w[i] = w[i - 1] * 26;
}
int Up = 0;
for (int i = Maxn; i >= 1; i--) {
if (n < w[i]) continue;
if (Up == 0) Up = i;
k[i] = n / w[i];
n %= w[i];
}
for (int i = Up; i >= 1; i--) {
int p = i;
while (k[p] == 0 && k[p + 1] > 0) {
k[p] += 26;
k[p + 1]--;
p++;
}
}
for (int i = Up; i >= 1; i--) {
if (k[i] != 0) printf ("%c", k[i] + 'a' - 1);
}
return 0;
}
2.Knapsack for All Subsets
我们有一个集合 T T T, c a r d ( T ) = t card (T) = t card(T)=t, 全集为 I I I, c a r d ( I ) = i card(I) = i card(I)=i,则 T T T 的超集个数为 2 i − t 2^{i - t} 2i−t(乘法原理易知)。
所以如果我们有一个集合 T T T, ∑ x ∈ T x = s \sum_{x \in T}x = s ∑x∈Tx=s,则它对答案的贡献为 2 n − c a r d ( T ) 2^{n-card(T)} 2n−card(T)。
看到 s ≤ 3000 s \leq 3000 s≤3000,想到用 d p dp dp 记录和为 i i i 的贡献和。
d p [ i ] [ j ] dp[i][j] dp[i][j] 表示在前 i i i 个数中选择,和为 j j j 的集合的贡献和。
d p [ i ] [ j ] = d p [ i − 1 ] [ j ] + d p [ i − 1 ] [ j − a [ i ] ] 2 dp[i][j] = dp[i - 1][j] + \frac{dp[i - 1][j - a[i]]}{2} dp[i][j]=dp[i−1][j]+2dp[i−1][j−a[i]](多了一个数 a [ i ] a[i] a[i],所以贡献需要除以 2 2 2)。
第一维由于只有相邻的在转移,所以可以滚动。
#include <cmath>
#include <cstdio>
#include <iostream>
#include <algorithm>
#define int long long
#define LL long long
#define ULL unsigned long long
using namespace std;
const int Maxn = 3000;
const int Mod = 998244353;
int n, s, a[Maxn + 5];
int dp[Maxn + 5];
int quick_pow (int a, int b) {
int res = 1;
while (b) {
if (b & 1) res = (res * a) % Mod;
a = (a * a) % Mod; b >>= 1;
}
return res;
}
signed main () {
cin >> n >> s;
for (int i = 1; i <= n; i++)
cin >> a[i];
dp[0] = quick_pow (2, n);
for (int i = 1; i <= n; i++) {
for (int j = s; j >= a[i]; j--)
dp[j] = (dp[j] + dp[j - a[i]] * (Mod / 2 + 1)) % Mod;
}
cout << dp[s];
return 0;
}
3. Strivore
好心人的提示
在字符串 s s s 中插入 n n n 个小写字母,就相当于在 n + s . l e n g t h n+s.length n+s.length 个格子里面填入小写字母,要求其存在为 s s s 的子序列(不一定要连续)。
先确定 s s s 第一个字母所在的位置(大致思路),假设在位置 i i i 处,( 0 < i ≤ n + 1 0 < i \leq n+1 0<i≤n+1), i i i 前面的空格每一个都有 26 26 26 种情况,总共 ( 2 6 i − 1 ) (26^{i-1}) (26i−1)种情况, i i i 后面的空格,先选 s . l e n g t h − 1 s.length-1 s.length−1 各格子放入 s 的其他字母,有 ( C k − i l e n − 1 ) (C_{k-i}^{len-1}) (Ck−ilen−1)种方法,然后每个 s 串字母后面不选相同的,用于去重,有 ( 2 5 k − i − l e n + 1 ) (25^{k-i-len+1} ) (25k−i−len+1) 种情况。
不要问我 T a Ta Ta 在讲什么,我也没太看懂,但只要知道思路就行了
为了证明其正确性,我们仅需要证明这样的方法 不重不漏
不重
十分简单,第二个数列中的第 i i i 个空一定不是 s [ 1 ] s[1] s[1],所以一定不会重复
不漏
灵感来自 yxc大巨佬,我不是在打广告啊
我们可以利用集合的思想来解决
一目了然,每一个区域对应着枚举的
i
i
i所对应的方案数,所以没有漏掉任何情况
60 p t s 60pts 60pts
逆元加上快速幂乱搞
#include <cmath>
#include <stack>
#include <queue>
#include <vector>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <algorithm>
#define LL long long
#define ULL unsigned long long
using namespace std;
template <typename T> int read (T &x) {x = 0; T f = 1;char tem = getchar ();while (tem < '0' || tem > '9') {if (tem == '-') f = -1;tem = getchar ();}while (tem >= '0' && tem <= '9') {x = (x << 1) + (x << 3) + tem - '0';tem = getchar ();}x *= f; return 1;}
template <typename T> void write (T x) {if (x < 0) {x = -x;putchar ('-');}if (x > 9) write (x / 10);putchar (x % 10 + '0');}
template <typename T> T Max (T x, T y) { return x > y ? x : y; }
template <typename T> T Min (T x, T y) { return x < y ? x : y; }
template <typename T> T Abs (T x) { return x > 0 ? x : -x; }
const int Maxn = 1e6;
const LL Mod = 1e9 + 7;
int n;
LL fac[Maxn * 2 + 5], w1[Maxn * 2 + 5], w2[Maxn * 2 + 5], inv_fac[Maxn * 2 + 5];
string s;
void exgcd (LL a, LL b, LL &x, LL &y) {
if (b == 0) {
x = 1; y = 0;
return;
}
exgcd (b, a % b, y, x);
y -= a / b * x;
}
LL inv (LL a) {
LL x, y;
exgcd (a, Mod, x, y);
x = ((x % Mod) + Mod) % Mod;
return x;
}
LL C (LL a, LL b) {
return fac[b] * inv_fac[a] % Mod * inv_fac[b - a] % Mod;
}
int main () {
cin >> n >> s;
fac[0] = 1; w1[0] = 1; w2[0] = 1;
for (int i = 1; i <= n + s.length () + 1; i++) {
fac[i] = fac[i - 1] * i;
fac[i] %= Mod;
w1[i] = w1[i - 1] * 26;
w1[i] %= Mod;
w2[i] = w2[i - 1] * 25;
w2[i] %= Mod;
}
inv_fac[n + s.length ()] = inv (fac[n + s.length()]);
for(int i = n + s.length () - 1; i >= 0; i--)
inv_fac[i] = inv_fac[i + 1] * (i + 1) % Mod;
LL ans = 0;
for (int i = 1; i <= n + 1; i++) {
ans += w1[i - 1] * C (s.length () - 1, n + s.length () - i) % Mod * w2[n - i + 1] % Mod;
ans %= Mod;
}
cout << ans;
return 0;
}
发现 n ∈ [ 1 , 1 e 6 ] n \in [1, 1e6] n∈[1,1e6],要卡 O ( n ∗ l o g 2 n ) O(n * log_2n) O(n∗log2n),所以只有打表打出幂。
但是还有一个问题,阶乘的逆元怎么求呢?
引入一个 N B NB NB 的东西,阶乘逆元
a
!
x
≡
1
(
m
o
d
p
)
(
a
=
i
)
a!x \equiv 1 \pmod {p} (a = i)
a!x≡1(modp)(a=i)
a
!
i
∗
y
≡
1
(
m
o
d
p
)
\frac {a!}{i} * y \equiv 1 \pmod p
ia!∗y≡1(modp)
令
y
=
i
x
y = ix
y=ix
即证
a
!
i
∗
i
x
≡
1
(
m
o
d
p
)
\frac {a!} {i} *ix \equiv 1 \pmod p
ia!∗ix≡1(modp)
即
(
a
−
1
)
!
∗
i
x
≡
1
(
m
o
d
p
)
(a-1)! *ix \equiv 1 \pmod p
(a−1)!∗ix≡1(modp)
即
a
!
x
≡
1
(
m
o
d
p
)
a!x \equiv1 \pmod p
a!x≡1(modp)
所以 ( a − 1 ) ! (a - 1)! (a−1)! 的逆元为 i x ix ix
#include <cmath>
#include <stack>
#include <queue>
#include <vector>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <algorithm>
#define LL long long
#define ULL unsigned long long
using namespace std;
template <typename T> int read (T &x) {x = 0; T f = 1;char tem = getchar ();while (tem < '0' || tem > '9') {if (tem == '-') f = -1;tem = getchar ();}while (tem >= '0' && tem <= '9') {x = (x << 1) + (x << 3) + tem - '0';tem = getchar ();}x *= f; return 1;}
template <typename T> void write (T x) {if (x < 0) {x = -x;putchar ('-');}if (x > 9) write (x / 10);putchar (x % 10 + '0');}
template <typename T> T Max (T x, T y) { return x > y ? x : y; }
template <typename T> T Min (T x, T y) { return x < y ? x : y; }
template <typename T> T Abs (T x) { return x > 0 ? x : -x; }
const int Maxn = 1e6;
const LL Mod = 1e9 + 7;
int n;
LL fac[Maxn * 2 + 5], w1[Maxn * 2 + 5], w2[Maxn * 2 + 5], inv_fac[Maxn * 2 + 5];
string s;
void exgcd (LL a, LL b, LL &x, LL &y) {
if (b == 0) {
x = 1; y = 0;
return;
}
exgcd (b, a % b, y, x);
y -= a / b * x;
}
LL inv (LL a) {
LL x, y;
exgcd (a, Mod, x, y);
x = ((x % Mod) + Mod) % Mod;
return x;
}
LL C (LL a, LL b) {
return fac[b] * inv_fac[a] % Mod * inv_fac[b - a] % Mod;
}
int main () {
cin >> n >> s;
fac[0] = 1; w1[0] = 1; w2[0] = 1;
for (int i = 1; i <= n + s.length () + 1; i++) {
fac[i] = fac[i - 1] * i;
fac[i] %= Mod;
w1[i] = w1[i - 1] * 26;
w1[i] %= Mod;
w2[i] = w2[i - 1] * 25;
w2[i] %= Mod;
}
inv_fac[n + s.length ()] = inv (fac[n + s.length()]);
for(int i = n + s.length () - 1; i >= 0; i--)
inv_fac[i] = inv_fac[i + 1] * (i + 1) % Mod;
LL ans = 0;
for (int i = 1; i <= n + 1; i++) {
ans += w1[i - 1] * C (s.length () - 1, n + s.length () - i) % Mod * w2[n - i + 1] % Mod;
ans %= Mod;
}
cout << ans;
return 0;
}