文章目录
-
- 零、写在前面
- [一、MC0547 子胥渡江计数](#一、MC0547 子胥渡江计数)
-
- [1.1 原题链接](#1.1 原题链接)
- [1.2 思路分析](#1.2 思路分析)
- [1.3 AC代码](#1.3 AC代码)
- [二、MC0548 公子密会子胥](#二、MC0548 公子密会子胥)
-
- [2.1 原题链接](#2.1 原题链接)
- [2.2 思路分析](#2.2 思路分析)
- [2.3 AC代码](#2.3 AC代码)
- [三、MC0549 专诸市井被荐](#三、MC0549 专诸市井被荐)
-
- [3.1 原题链接](#3.1 原题链接)
- [3.2 思路分析](#3.2 思路分析)
- [3.3 AC代码](#3.3 AC代码)
- [四、MC0550 鱼肠剑试锋芒](#四、MC0550 鱼肠剑试锋芒)
-
- [4.1 原题链接](#4.1 原题链接)
- [4.2 思路分析](#4.2 思路分析)
- [4.3 AC代码](#4.3 AC代码)
- [五、MC0551 炙鱼术隐杀机](#五、MC0551 炙鱼术隐杀机)
-
- [5.1 原题链接](#5.1 原题链接)
- [5.2 思路分析](#5.2 思路分析)
- [5.3 AC代码](#5.3 AC代码)
- [六、MC0552 伏兵暗室设宴](#六、MC0552 伏兵暗室设宴)
-
- [6.1 原题链接](#6.1 原题链接)
- [6.2 思路分析](#6.2 思路分析)
- [6.3 AC代码](#6.3 AC代码)
- [七、MC0553 鱼腹剑刺王僚](#七、MC0553 鱼腹剑刺王僚)
-
- [7.1 原题链接](#7.1 原题链接)
- [7.2 思路分析](#7.2 思路分析)
- [7.3 AC代码](#7.3 AC代码)
- [八、MC0554 阖闾立吴赏功](#八、MC0554 阖闾立吴赏功)
-
- [8.1 原题链接](#8.1 原题链接)
- [8.2 思路分析](#8.2 思路分析)
- [8.3 AC代码](#8.3 AC代码)
- [九、MC0555 子胥谋楚伐兵](#九、MC0555 子胥谋楚伐兵)
-
- [9.1 原题链接](#9.1 原题链接)
- [9.2 思路分析](#9.2 思路分析)
- [9.3 AC代码](#9.3 AC代码)
- [十、MC0556 掘墓统计铭文](#十、MC0556 掘墓统计铭文)
-
- [10.1 原题链接](#10.1 原题链接)
- [10.2 思路分析](#10.2 思路分析)
- [10.3 AC代码](#10.3 AC代码)
零、写在前面
毕竟是省赛,基本都是经典算法题,拿来练手还是不错的。
题解写的比较简略。
一、MC0547 子胥渡江计数
1.1 原题链接
1.2 思路分析
签到
1.3 AC代码
python
n = int(input())
f = g = 0
for i in range(n):
a, b = map(int, input().split())
if b >= a: f += 1
else: g += 1
print(f, g)
二、MC0548 公子密会子胥
2.1 原题链接
2.2 思路分析
签到
2.3 AC代码
cpp
#include <bits/stdc++.h>
using i64 = long long;
using u32 = unsigned;
using u64 = unsigned long long;
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
int n;
std::cin >> n;
int min = 1E9;
for (int i = 0; i < n; ++i) {
int x;
std::cin >> x;
min = std::min(min, x);
std::cout << min << " \n"[i == n - 1];
}
return 0;
}
三、MC0549 专诸市井被荐
3.1 原题链接
3.2 思路分析
签到
3.3 AC代码
cpp
#include <bits/stdc++.h>
using i64 = long long;
using u32 = unsigned;
using u64 = unsigned long long;
constexpr int P = 10007;
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
int n, m;
std::cin >> n >> m;
std::vector<int> a(n);
for (int i = 0; i < n; ++i) {
std::cin >> a[i];
}
std::sort(a.begin(), a.end());
int L = a[0], R = a[n - 1];
int ans = 0;
for (; m > 0; --m) {
int l, r;
std::cin >> l >> r;
ans += std::max(0, std::min(n, R - r)) + std::max(0, std::min(n, l - L));
ans %= P;
}
std::cout << ans << "\n";
return 0;
}
四、MC0550 鱼肠剑试锋芒
4.1 原题链接
4.2 思路分析
除以1我们不管
然后剩下的最多除log次就变0了,数组全为0就不用操作了
4.3 AC代码
cpp
#include <bits/stdc++.h>
using i64 = long long;
using u32 = unsigned;
using u64 = unsigned long long;
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
int n, q;
std::cin >> n >> q;
std::vector<int> a(n);
int ans = 0;
for (int i = 0; i < n; ++i) {
std::cin >> a[i];
ans += a[i];
}
bool ok = true;
for (; q > 0; --q) {
int x;
std::cin >> x;
if (x > 1 && ok) {
ok = false;
for (int &y : a) {
ans -= y;
y /= x;
ans += y;
if (y > 0) {
ok = true;
}
}
}
std::cout << ans << '\n';
}
return 0;
}
五、MC0551 炙鱼术隐杀机
5.1 原题链接
5.2 思路分析
题意就是给一个卷积核,在一个矩阵上做异或的卷积运算
似乎很难优化,但想一想深度学习中那种Hadamard乘积都没法突破复杂度,这个如果能做肯定是从异或运算本身考虑
卷积核中每个数都会扫过原矩阵中的一个矩形区域
如果卷积核和矩阵都是01构成的,那么对于卷积核中的一个0,只需要看他扫的矩形区域里面有多少1,反之亦然
那么我们按位考虑,每次对矩阵处理前缀和,来快速查找区域里面1的个数
5.3 AC代码
cpp
#include <bits/stdc++.h>
using i64 = long long;
using u32 = unsigned;
using u64 = unsigned long long;
constexpr int P = 1'000'000'007;
int add(int x, int y) {
x += y - P;
x += (x >> 31) & P;
return x;
}
int mul(int x, int y) {
return 1LL * x * y % P;
}
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
int n, m;
std::cin >> n >> m;
std::vector<std::vector<int>> a(n, std::vector<int>(n));
std::vector<std::vector<int>> b(m, std::vector<int>(m));
for (int i = 0; i < n; ++i) {
for (int j = 0; j < n; ++j) {
std::cin >> a[i][j];
}
}
for (int i = 0; i < m; ++i) {
for (int j = 0; j < m; ++j) {
std::cin >> b[i][j];
}
}
int ans = 0;
for (int p = 0; p < 30; ++p) {
std::vector<std::vector<int>> f(n, std::vector<int>(n));
auto get = [&](int i, int j) {
if (i < 0 || j < 0 || i >= n || j >= n) {
return 0;
}
return f[i][j];
};
for (int i = 0; i < n; ++i) {
for (int j = 0; j < n; ++j) {
f[i][j] = add(a[i][j] >> p & 1, add(get(i - 1, j), add(get(i, j - 1), P - get(i - 1, j - 1))));
}
}
for (int i = 0; i < m; ++i) {
for (int j = 0; j < m; ++j) {
int c1 = add(get(n - m + i, n - m + j), add(get(i - 1, j - 1), add(P - get(i - 1, n - (m - j)), P - get(n - (m - i), j - 1))));
int c0 = (n - m + 1) * (n - m + 1) - c1;
if (b[i][j] >> p & 1) {
ans = add(ans, mul(c0, 1 << p));
} else {
ans = add(ans, mul(c1, 1 << p));
}
}
}
}
std::cout << ans << '\n';
return 0;
}
六、MC0552 伏兵暗室设宴
6.1 原题链接
6.2 思路分析
货物运输问题 + 贡献法
经典问题,两个长度为n且数组和相等的数组a,b,每次可以将a[i] +1, a[i + 1]-1 或者
a[i]- 1,a[i + 1]+1,问多少次操作可以使得两个数组相等
答案为 Σ|sa[i]- sb[i],其中sa和sb分别为a和b的前缀和数组
对于本题,我们只需考虑 sa[i]- sb[i] 会在多少个(s,t)中出现然后计算其贡献即可
这个不难用组合数学计算出前i个位置放j个1的字符串t的数目
6.3 AC代码
cpp
#include <bits/stdc++.h>
using i64 = long long;
using u32 = unsigned;
using u64 = unsigned long long;
constexpr int P = 1E9 + 7;
int add(int x, int y) {
x += y - P;
x += (x >> 31) & P;
return x;
}
int mul(int x, int y) {
return 1LL * x * y % P;
}
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
int n;
std::cin >> n;
std::vector<std::vector<int>> binom(n + 1, std::vector<int>(n + 1));
for (int i = 0; i <= n; ++i) {
binom[i][0] = 1;
for (int j = 1; j <= i; ++j) {
binom[i][j] = add(binom[i - 1][j - 1], binom[i - 1][j]);
}
}
std::string s;
std::cin >> s;
std::vector<int> cnt(n);
for (int i = 0; i < n; ++i) {
cnt[i] = s[i] - '0';
if (i > 0) {
cnt[i] += cnt[i - 1];
}
}
int ans = 0;
for (int i = 0; i < n; ++i) {
for (int j = 0; j <= std::min(cnt[n - 1], i + 1); ++j) {
int l = j, r = cnt[n - 1] - j;
if (l > i + 1 || r > n - i - 1) {
continue;
}
ans = add(ans, mul(std::abs(cnt[i] - j), mul(binom[i + 1][l], binom[n - i - 1][r])));
}
}
std::cout << ans << '\n';
return 0;
}
七、MC0553 鱼腹剑刺王僚
7.1 原题链接
7.2 思路分析
经典贪心
因为m不会很大,我们直接排序,堆模拟三元组,每次取出最小那个,然后扩展。
堆模拟的过程中,限制
(ij, k) => (i j, k + 1)
(i,j, 0) => (ij+ 1,0)
(i,0, 0)=> (i+ 1,0, 0)
可保证不重不漏,省去哈希表/平衡树的额外开销
7.3 AC代码
cpp
#include <bits/stdc++.h>
using i64 = long long;
using u32 = unsigned;
using u64 = unsigned long long;
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
int T;
std::cin >> T;
while (T-- > 0) {
int n, m;
std::cin >> n >> m;
std::vector<int> a(n), b(n), c(n);
for (int i = 0; i < n; i++) {
std::cin >> a[i];
}
for (int i = 0; i < n; i++) {
std::cin >> b[i];
}
for (int i = 0; i < n; i++) {
std::cin >> c[i];
}
std::sort(a.begin(), a.end());
std::sort(b.begin(), b.end());
std::sort(c.begin(), c.end());
std::priority_queue<std::array<i64, 4>, std::vector<std::array<i64, 4>>, std::greater<>> h;
h.push({1LL * a[0] * b[0] * c[0], 0, 0, 0});
for (; m > 0; --m) {
auto [x, i, j, k] = h.top();
h.pop();
if (j + k == 0 && i + 1 < n) {
h.push({1LL * a[i + 1] * b[j] * c[k], i + 1, j, k});
}
if (k == 0 && j + 1 < n) {
h.push({1LL * a[i] * b[j + 1] * c[k], i, j + 1, k});
}
if (k + 1 < n) {
h.push({1LL * a[i] * b[j] * c[k + 1], i, j, k + 1});
}
std::cout << x << " \n"[m == 1];
}
}
return 0;
}
八、MC0554 阖闾立吴赏功
8.1 原题链接
8.2 思路分析
ODT 维护颜色段
将初始连续段插入map,对于推方块操作,以从x往右推L为例,如果操作过程中有碰到方
块,那么我们可以删除掉操作过程中所有触碰到的颜色段,然后在x+L处插入合并后的线段
操作过程中如何维护颜色段合并?
初始线段长度c=0,右端点为r=x+L+c,,然后从左到右依次遍历map中左端点不小于r的颜色段,累加颜色段长度到c
复杂度分析: 瓶颈在于我们操作过程中遍历到的颜色段数目,由于每次操作前会split,最多
增加1个线段,合并过程中颜色段数目不增,那么我们遍历的颜色段为 O(n + m) 量级,总体复杂度为 O((n + m) logn)
8.3 AC代码
cpp
#include <bits/stdc++.h>
using i64 = long long;
using u32 = unsigned;
using u64 = unsigned long long;
constexpr i64 inf = 5E9;
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
int n, m;
std::cin >> n >> m;
std::map<i64, int> f;
std::vector<int> x(n);
for (int i = 0; i < n; ++i) {
std::cin >> x[i];
}
std::sort(x.begin(), x.end());
for (int i = 0, j = 0; i < n; ) {
j = i + 1;
while (j < n && x[j] == x[j - 1] + 1) {
++j;
}
f[x[i]] = j - i;
i = j;
}
f[-inf] = f[inf] = 0;
auto split = [&](i64 x) -> void {
auto it = std::prev(f.upper_bound(x));
if (it->first != x) {
i64 r = it->first + it->second;
if (r > x) {
it->second = x - it->first;
f[x] = r - x;
}
}
};
for (; m > 0; --m) {
int op, L;
i64 x;
std::cin >> op >> x >> L;
if (op == 1) {
split(x);
i64 r = x + L;
int c = 0;
for (auto it = f.lower_bound(x); it->first <= r + c; ) {
c += it->second;
it = f.erase(it);
}
if (c > 0) {
f[r] = c;
}
} else {
split(x);
i64 l = x - L;
int c = 0;
for (auto it = std::prev(f.lower_bound(x)); it->first + it->second >= l - c; ) {
c += it->second;
it = f.erase(it);
--it;
}
if (c > 0) {
f[l - c] = c;
}
}
}
f.erase(inf);
f.erase(-inf);
std::vector<i64> p;
for (auto &[x, c] : f) {
for (int i = 0; i < c; ++i) {
p.push_back(x + i);
}
}
assert(p.size() == n);
for (int i = 0; i < n; ++i) {
std::cout << p[i] << " \n"[i + 1 == n];
}
return 0;
}
九、MC0555 子胥谋楚伐兵
9.1 原题链接
9.2 思路分析
meet in middle + 预处理 + 01Trie + 双指针
注意n = 22时,带距离限制的子集数不会很大,我们考虑递推预处理左右两半的子集异或
和,然后双指针枚举右 +维护左,字典树维护当前插入的左半异或和,然后就是01Trie经典问题:给定x,有多少个数和x的异或值不大于y
9.3 AC代码
cpp
#include <bits/stdc++.h>
using i64 = long long;
using u32 = unsigned;
using u64 = unsigned long long;
constexpr int N = 1 << 24;
constexpr int B = 30;
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
int n, d, w;
std::cin >> n >> d >> w;
int m = n - n / 2;
n -= m;
std::vector<int> a(n), b(m);
for (int i = 0; i < n; ++i) {
std::cin >> a[i];
}
for (int i = 0; i < m; ++i) {
std::cin >> b[i];
}
std::vector<std::array<int, 2>> ch(N);
std::vector<int> cnt(N);
int tot = 1;
auto add = [&](int x) {
int p = 1;
for (int b = B - 1; b >= 0; --b) {
int c = x >> b & 1;
if (!ch[p][c]) {
ch[p][c] = ++tot;
}
p = ch[p][c];
++cnt[p];
}
};
i64 ans = 0;
std::vector<std::vector<int>> f(n), g(m);
for (int i = 0; i < n; ++i) {
f[i].push_back(a[i]);
if (a[i] <= w) {
++ans;
}
for (int j = 0; j <= i - d; ++j) {
for (int x : f[j]) {
f[i].push_back(x ^ a[i]);
if (f[i].back() <= w) {
++ans;
}
}
}
}
for (int i = m - 1; i >= 0; --i) {
g[i].push_back(b[i]);
if (b[i] <= w) {
++ans;
}
for (int j = i + d; j < m; ++j) {
for (int x : g[j]) {
g[i].push_back(x ^ b[i]);
if (g[i].back() <= w) {
++ans;
}
}
}
}
for (int i = 0, j = 0; i < m; ++i) {
while (j < n && n + i >= j + d) {
for (int x : f[j]) {
add(x);
}
++j;
}
for (int x : g[i]) {
int p = 1;
for (int b = B - 1; b >= 0; --b) {
if (w >> b & 1) {
ans += cnt[ch[p][x >> b & 1]];
p = ch[p][x >> b & 1 ^ 1];
} else {
p = ch[p][x >> b & 1];
}
if (p == 0) {
break;
}
}
ans += cnt[p];
}
}
std::cout << ans << "\n";
return 0;
}
十、MC0556 掘墓统计铭文
10.1 原题链接
10.2 思路分析
签到
10.3 AC代码
cpp
#include <bits/stdc++.h>
using i64 = long long;
using u32 = unsigned;
using u64 = unsigned long long;
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
std::string s;
std::cin >> s;
i64 ans = 0;
for (auto c : s) {
ans += c;
}
std::cout << ans << '\n';
return 0;
}