补题链接:Here

A - Plural Form

字符串,末尾有 s 的加es,不然加 s .

B - Go to Jail

输入的时候判断一下是否连续相等即可

C - A x B + C (math,欧拉筛)

题意:请问能找到多少组 (A,B,C)= N .

思路一:对于使得 AtCoder Beginner Contest 179 个人题解(C欧拉筛,D前缀和,E循环节,F线段树) - 图1(A,B) 都会存在唯一值 C 所以我们只需要对 (A,B) 计数即可

AtCoder Beginner Contest 179 个人题解(C欧拉筛,D前缀和,E循环节,F线段树) - 图2 值确定时,AtCoder Beginner Contest 179 个人题解(C欧拉筛,D前缀和,E循环节,F线段树) - 图3AtCoder Beginner Contest 179 个人题解(C欧拉筛,D前缀和,E循环节,F线段树) - 图4 。所以我们遍历 AtCoder Beginner Contest 179 个人题解(C欧拉筛,D前缀和,E循环节,F线段树) - 图5 ~ AtCoder Beginner Contest 179 个人题解(C欧拉筛,D前缀和,E循环节,F线段树) - 图6 即可。

AC 代码

  1. int main() {
  2. ios_base::sync_with_stdio(false), cin.tie(0);
  3. int n;
  4. cin >> n;
  5. ll cnt = 0;
  6. for (int i = 1; i <= n; ++i) cnt += (n - 1) / i;
  7. cout << cnt << "\n";
  8. return 0;
  9. }

思路二:

另外在 CP wiki上也解释了这道题可以计算 AtCoder Beginner Contest 179 个人题解(C欧拉筛,D前缀和,E循环节,F线段树) - 图7 的因子数来解决

固定 AtCoder Beginner Contest 179 个人题解(C欧拉筛,D前缀和,E循环节,F线段树) - 图8,符合条件的二元组 AtCoder Beginner Contest 179 个人题解(C欧拉筛,D前缀和,E循环节,F线段树) - 图9#card=math&code=%28A%2CB%29) 的数目就等于 AtCoder Beginner Contest 179 个人题解(C欧拉筛,D前缀和,E循环节,F线段树) - 图10 的因子数,而一个正整数的因子数可以通过质因数分解求得。

设:AtCoder Beginner Contest 179 个人题解(C欧拉筛,D前缀和,E循环节,F线段树) - 图11,则其因子数为 AtCoder Beginner Contest 179 个人题解(C欧拉筛,D前缀和,E循环节,F线段树) - 图12#card=math&code=%E2%88%8F_%7Bi%20%3D%201%7D%5En%28a_i%20%2B%201%29)

所以我们就计算 AtCoder Beginner Contest 179 个人题解(C欧拉筛,D前缀和,E循环节,F线段树) - 图13 每个数的因子数,然后求和即可。

因为AtCoder Beginner Contest 179 个人题解(C欧拉筛,D前缀和,E循环节,F线段树) - 图14范围内的质数个数近似为 AtCoder Beginner Contest 179 个人题解(C欧拉筛,D前缀和,E循环节,F线段树) - 图15,总时间复杂度为 AtCoder Beginner Contest 179 个人题解(C欧拉筛,D前缀和,E循环节,F线段树) - 图16#card=math&code=O%28%5Cfrac%7BN%5Csqrt%7BN%7D%7D%7B%5Clog%20N%7D%29)。

  1. #include <bits/stdc++.h>
  2. using namespace std;
  3. using ll = long long;
  4. using namespace std;
  5. int main() {
  6. int n;
  7. cin >> n;
  8. vector<int> primes = {2, 3, 5, 7, 11, 13};
  9. for (int i = 17; i <= n - 1; i += 2) {
  10. bool is_prime = true;
  11. for (int j = 0; primes[j] * primes[j] <= i; ++j) {
  12. if (i % primes[j] == 0) {
  13. is_prime = false;
  14. break;
  15. }
  16. }
  17. if (is_prime)
  18. primes.emplace_back(i);
  19. }
  20. int ans = 0;
  21. for (int i = 1; i < n; ++i) {
  22. int k = i;
  23. int fac = 1;
  24. for (int j = 0; primes[j] * primes[j] <= k; ++j) {
  25. if (k % primes[j] == 0) {
  26. int cnt = 0;
  27. while (k % primes[j] == 0)
  28. cnt++, k /= primes[j];
  29. fac *= (cnt + 1);
  30. }
  31. }
  32. if (k != 1) fac *= 2;
  33. ans += fac;
  34. }
  35. cout << ans;
  36. }

思路三:

  1. 欧拉筛,令f[i]为a*b=i的方案数,
  2. f[i]的前缀和就是a*b<=i的方案数.
  3. 又因为题目中a*b+cc必须>=1,那么我们输出f[n-1]即可,原理是留了一个1c.

AC 代码

  1. #include <bits/stdc++.h>
  2. typedef long long ll;
  3. using namespace std;
  4. const ll maxn = 2e6 + 7;
  5. ll f[maxn], n;
  6. signed main() {
  7. ios_base::sync_with_stdio(false), cin.tie(0);
  8. for (int i = 1; i < maxn; ++i)
  9. for (int j = i; j < maxn; j += i) f[j] += 1;
  10. for (int i = 1; i < maxn; ++i) f[i] += f[i - 1];
  11. cin >> n;
  12. cout << f[n - 1] << "\n";
  13. return 0;
  14. }

D - Leaping Tak

题意:

AtCoder Beginner Contest 179 个人题解(C欧拉筛,D前缀和,E循环节,F线段树) - 图17 个房间排成一行,房间编号为 AtCoder Beginner Contest 179 个人题解(C欧拉筛,D前缀和,E循环节,F线段树) - 图18。目前高桥所在房间为 AtCoder Beginner Contest 179 个人题解(C欧拉筛,D前缀和,E循环节,F线段树) - 图19 ,现在他想移动到 AtCoder Beginner Contest 179 个人题解(C欧拉筛,D前缀和,E循环节,F线段树) - 图20 号房间。

给定 AtCoder Beginner Contest 179 个人题解(C欧拉筛,D前缀和,E循环节,F线段树) - 图21 个不相交的区间,而集合 AtCoder Beginner Contest 179 个人题解(C欧拉筛,D前缀和,E循环节,F线段树) - 图22 是这 AtCoder Beginner Contest 179 个人题解(C欧拉筛,D前缀和,E循环节,F线段树) - 图23 个区间的并集。移动规则如下:

  • 高桥在房间 AtCoder Beginner Contest 179 个人题解(C欧拉筛,D前缀和,E循环节,F线段树) - 图24,可以从集合 AtCoder Beginner Contest 179 个人题解(C欧拉筛,D前缀和,E循环节,F线段树) - 图25 中选择一个整数 AtCoder Beginner Contest 179 个人题解(C欧拉筛,D前缀和,E循环节,F线段树) - 图26,这样可以移动到房间 AtCoder Beginner Contest 179 个人题解(C欧拉筛,D前缀和,E循环节,F线段树) - 图27
  • 高桥不可以移动到房间以外。

思路:

考虑我们当前处于 AtCoder Beginner Contest 179 个人题解(C欧拉筛,D前缀和,E循环节,F线段树) - 图28 位置,我们上一步可能来自哪里?

我们需要检查所有的区间,然后找出对应的区间 AtCoder Beginner Contest 179 个人题解(C欧拉筛,D前缀和,E循环节,F线段树) - 图29(如果这样的区间存在的话),这一区间包含了所有能够利用原区间中的步长跳跃到当前位置的位置,也即上一步可能的位置。接下来我们将 AtCoder Beginner Contest 179 个人题解(C欧拉筛,D前缀和,E循环节,F线段树) - 图30#card=math&code=sum%28l%2Cr%29)累加到 AtCoder Beginner Contest 179 个人题解(C欧拉筛,D前缀和,E循环节,F线段树) - 图31上。

因为我们需要快速计算 AtCoder Beginner Contest 179 个人题解(C欧拉筛,D前缀和,E循环节,F线段树) - 图32#card=math&code=sum%28l%2Cr%29),所以实际上我们可以使用前缀和 AtCoder Beginner Contest 179 个人题解(C欧拉筛,D前缀和,E循环节,F线段树) - 图33,而非 AtCoder Beginner Contest 179 个人题解(C欧拉筛,D前缀和,E循环节,F线段树) - 图34

  • AtCoder Beginner Contest 179 个人题解(C欧拉筛,D前缀和,E循环节,F线段树) - 图35#card=math&code=%5Cmathcal%7BO%7D%28NK%29)
  1. #include <bits/stdc++.h>
  2. using ll = long long;
  3. using namespace std;
  4. const ll mod = 998244353;
  5. int main() {
  6. ios_base::sync_with_stdio(false), cin.tie(0);
  7. int n, k;
  8. cin >> n >> k;
  9. vector<pair<int, int>> v(k);
  10. for (int i = 0; i < k; ++i) cin >> v[i].first >> v[i].second;
  11. vector<ll> sum(n + 1, 0);
  12. sum[1] = 1;
  13. for (int i = 2; i <= n; ++i) {
  14. ll cur = 0;
  15. for (int j = 0; j < k; ++j) {
  16. // 如果这个区间的起点大于 i 就不可能 i 是由这个点移动到的了
  17. if (v[j].first >= i) continue;
  18. int l = max(1, i - v[j].second);
  19. int r = i - v[j].first;
  20. cur += sum[r] - sum[l - 1];
  21. }
  22. cur %= mod;
  23. sum[i] = (sum[i - 1] + cur + mod) % mod;
  24. }
  25. cout << (sum[n] - sum[n - 1] + mod) % mod;
  26. return 0;
  27. }

E - Sequence Sum

显然,操作足够多次后,一定会开始循环。因为 AtCoder Beginner Contest 179 个人题解(C欧拉筛,D前缀和,E循环节,F线段树) - 图36,所以循环的长度不超过 AtCoder Beginner Contest 179 个人题解(C欧拉筛,D前缀和,E循环节,F线段树) - 图37,因此我们直接模拟找出循环节即可。

注意:循环节不一定是 x 为起点

  • AtCoder Beginner Contest 179 个人题解(C欧拉筛,D前缀和,E循环节,F线段树) - 图38#card=math&code=%5Cmathcal%7BO%7D%28M%29)
  1. #include <bits/stdc++.h>
  2. using ll = long long;
  3. using namespace std;
  4. int main() {
  5. ios_base::sync_with_stdio(false), cin.tie(0);
  6. ll n, x, m;
  7. cin >> n >> x >> m;
  8. map<ll, ll> mp;
  9. int idx = 0;
  10. vector<ll> path;
  11. do {
  12. mp[x] = idx++;
  13. path.emplace_back(x);
  14. x = x * x % m;
  15. } while (!mp.count(x));
  16. int start = mp[x];
  17. int len = idx - start;
  18. ll sum = 0;
  19. // 循环节之和
  20. for (int i = start; i < idx; ++i) sum += path[i];
  21. ll ans = 0;
  22. if (n < start) // 可能未进入循环节就结束累加了
  23. for (int i = 0; i < n; ++i) ans += path[i];
  24. else {
  25. for (int i = 0; i < start; ++i) ans += path[i];
  26. ll loop = (n - start) / len;
  27. ans += loop * sum;
  28. ll res = (n - start) % len; // 多余一节
  29. for (int i = 0; i < res; ++i) ans += path[start + i];
  30. }
  31. cout << ans << "\n";
  32. return 0;
  33. }

F - Simplified Reversi

当我们放一个白石头的时候,会改变多少黑石头的颜色?

对于第一种操作,这是由该列最上面的白石头决定的;对于第二种操作,这是由该行最左边的白石头决定的。

而每种操作的影响是什么呢?第一种操作会影响 AtCoder Beginner Contest 179 个人题解(C欧拉筛,D前缀和,E循环节,F线段树) - 图39 行( AtCoder Beginner Contest 179 个人题解(C欧拉筛,D前缀和,E循环节,F线段树) - 图40 是操作的列最上面的白石头所在的行),而第二种操作会影响 AtCoder Beginner Contest 179 个人题解(C欧拉筛,D前缀和,E循环节,F线段树) - 图41列(AtCoder Beginner Contest 179 个人题解(C欧拉筛,D前缀和,E循环节,F线段树) - 图42是操作的行最左边的白石头所在的列)。

很自然地想到用线段树来处理。一个储存每行最上面的白石头,另一个储存每列最左边的白石头。对于非叶结点,我们存储区间的最大值。因为我们每次操作相当于一个切割,把所有大于XX的数都变为XX,因此,存储区间最大值,可以让我们很方便地判断当前的切割是否产生了影响。

  • AtCoder Beginner Contest 179 个人题解(C欧拉筛,D前缀和,E循环节,F线段树) - 图43#card=math&code=%5Cmathcal%7BO%7D%28QlogN%29).
  1. #include <bits/stdc++.h>
  2. using ll = long long;
  3. using namespace std;
  4. #define MAXN 200005
  5. #define lson (idx << 1)
  6. #define rson (idx << 1 | 1)
  7. using namespace std;
  8. typedef long long ll;
  9. struct Node {
  10. int l, r, hi, lazy = 0;
  11. };
  12. struct SegTree {
  13. Node s[MAXN << 2];
  14. void calc(int idx) { s[idx].hi = max(s[lson].hi, s[rson].hi); }
  15. void pushdown(int idx) {
  16. if (s[idx].lazy) {
  17. for (int i = lson; i <= rson; ++i) {
  18. if (s[i].hi > s[idx].lazy) {
  19. s[i].hi = s[idx].lazy;
  20. s[i].lazy = s[idx].lazy;
  21. }
  22. }
  23. }
  24. s[idx].lazy = 0;
  25. }
  26. void build(int idx, int l, int r, vector<int> &a) {
  27. s[idx].l = l, s[idx].r = r;
  28. if (l == r) {
  29. s[idx].hi = a[l];
  30. return;
  31. }
  32. int mid = (l + r) >> 1;
  33. build(lson, l, mid, a);
  34. build(rson, mid + 1, r, a);
  35. calc(idx);
  36. }
  37. void update(int idx, int l, int r, int x) {
  38. if (s[idx].hi <= x)
  39. return;
  40. if (s[idx].l >= l && s[idx].r <= r) {
  41. s[idx].hi = x;
  42. s[idx].lazy = x;
  43. return;
  44. }
  45. pushdown(idx);
  46. int mid = (s[idx].l + s[idx].r) >> 1;
  47. if (l <= mid)
  48. update(lson, l, r, x);
  49. if (mid + 1 <= r)
  50. update(rson, l, r, x);
  51. calc(idx);
  52. }
  53. int query(int idx, int l, int r) {
  54. if (s[idx].l >= l && s[idx].r <= r)
  55. return s[idx].hi;
  56. pushdown(idx);
  57. int ans = 0;
  58. int mid = (s[idx].l + s[idx].r) >> 1;
  59. if (l <= mid)
  60. ans = max(ans, query(lson, l, r));
  61. if (mid + 1 <= r)
  62. ans = max(ans, query(rson, l, r));
  63. return ans;
  64. }
  65. };
  66. int main() {
  67. int n, q;
  68. cin >> n >> q;
  69. vector<int> col(n + 1, n), row(n + 1, n);
  70. col[n] = 1, row[n] = 1;
  71. SegTree C, R;
  72. C.build(1, 1, n, col);
  73. R.build(1, 1, n, row);
  74. ll ans = (ll)(n - 2) * (n - 2);
  75. for (int i = 0; i < q; ++i) {
  76. int t, x;
  77. cin >> t >> x;
  78. if (t == 1) {
  79. int hi = C.query(1, x, x);
  80. ans -= hi - 2;
  81. R.update(1, 1, hi, x);
  82. C.update(1, x, x, 1);
  83. } else {
  84. int hi = R.query(1, x, x);
  85. ans -= hi - 2;
  86. C.update(1, 1, hi, x);
  87. R.update(1, x, x, 1);
  88. }
  89. }
  90. cout << ans;
  91. }