目录
[A - Dora's Set](#A - Dora's Set)
[B - Index and Maximum Value](#B - Index and Maximum Value)
[C - Dora and C++](#C - Dora and C++)
[D - Iris and Game on the Tree](#D - Iris and Game on the Tree)
[E - Iris and the Tree](#E - Iris and the Tree)
A - Dora's Set
原题链接
思路分析
显然不能选两个偶数
比较简单的结论:
任意相邻的两个数一定互质
任意相邻的两个奇数一定互质
于是得出:
那么两个奇数 中间 夹一个偶数一定合法,并且这样得到的三元组是最多的
时间复杂度:O(N),当然可以做到O(1)
AC代码
cpp
#include <bits/stdc++.h>
using u32 = unsigned;
using i64 = long long;
using u64 = unsigned long long;
constexpr int inf32 = 1E9 + 7;
constexpr i64 inf64 = 1E18 + 7;
constexpr int P = 1'000'000'007;
void solve() {
int l, r;
std::cin >> l >> r;
int res = 0;
for (int i = l & 1 ? l : l + 1; i + 1 < r; i += 4) {
++ res;
}
std::cout << res << '\n';
}
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
int t = 1;
std::cin >> t;
while (t--) {
solve();
}
return 0;
}
B - Index and Maximum Value
原题链接
思路分析
我们注意到一个事情,由于每次操作都是对值域内的元素操作
那么从始至终,我们数组最大元素的下标始终不变(自己手玩一下就明白了)
时间复杂度:O(N)
AC代码
cpp
#include <bits/stdc++.h>
using u32 = unsigned;
using i64 = long long;
using u64 = unsigned long long;
constexpr int inf32 = 1E9 + 7;
constexpr i64 inf64 = 1E18 + 7;
constexpr int P = 1'000'000'007;
void solve() {
int n, m;
std::cin >> n >> m;
std::vector<int> a(n);
for (int i = 0; i < n; ++ i) std::cin >> a[i];
int ma = *std::max_element(a.begin(), a.end());
for (int i = 0; i < m; ++ i) {
char op;
int l, r;
std::cin >> op >> l >> r;
if (l <= ma && ma <= r)
ma += op == '+' ? 1 : -1;
std::cout << ma << " \n"[i + 1 == m];
}
}
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
int t = 1;
std::cin >> t;
while (t--) {
solve();
}
return 0;
}
C - Dora and C++
原题链接
思路分析
根据裴蜀定理:给定x,y存在ax + by = gcd(x, y)
那么我们可以实现对每个元素任意次 gcd(a, b) 加减操作
那么我们一定可以将原数组的元素都调整到 [0, gcd(a, b)) 内
显然我们可以得到的 range 不会超过 gcd(a, b)
我们将调整后的数组排序,枚举最小值 c[i]
为什么最小值可以取原数组的元素?
因为任意最优解我们都可以通过整个数组加减gcd来使得其最小值落在[0, gcd(a, b))内
那么枚举最小值后,最大值是哪个?
对于最小值c[i],最大值一定是c[i - 1] + gcd
很明显,不做证明
时间复杂度:O(NlogN)
AC代码
cpp
#include <bits/stdc++.h>
using u32 = unsigned;
using i64 = long long;
using u64 = unsigned long long;
constexpr int inf32 = 1E9 + 7;
constexpr i64 inf64 = 1E18 + 7;
constexpr int P = 1'000'000'007;
void solve() {
int n, a, b;
std::cin >> n >> a >> b;
int g = std::gcd(a, b);
std::vector<int> c(n);
for (int i = 0; i < n; ++ i) std::cin >> c[i], c[i] %= g;
std::ranges::sort(c);
int res = c[n - 1] - c[0];
for (int i = 1; i < n; ++ i) {
res = std::min(res, c[i - 1] + g - c[i]);
}
std::cout << res << '\n';
}
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
int t = 1;
std::cin >> t;
while (t--) {
solve();
}
return 0;
}
D - Iris and Game on the Tree
原题链接
思路分析
看起来很复杂,我们不妨先考虑比较好想的情况:根节点值确定
那么我们 Iris 一定将叶子尽可能染成和树根相反的值,Dora 一定尽可能将叶子染成和树根相同的值
记叶子中原来0的数目为c0,1的数目为c1,?为none
那么当root = 0时,Iris的收益就是 (none + 1) / 2 + c1
root=1不再赘述
当根节点值不确定:
这个时候我们发现,如果Iris先手调根,那么不管调几,Dora总能让Iris的收益减少
所以Iris要晚调根,同样的,Dora也要晚调根
那么当非叶子结点存在 ?时,二者都会先去调这些结点
这些结点数目的奇偶性决定了二者谁先手调根
所以我们分为两种情况,Iris先手调根:收益就是 max(c0, c1) + none / 2
当 可以使Dora先手的时候,收益是:min(c0, c1) + (none + 1) / 2
第二种情况可以取的时候需要和第一种情况取max
时间复杂度:O(N)
AC代码
cpp
#include <bits/stdc++.h>
using u32 = unsigned;
using i64 = long long;
using u64 = unsigned long long;
constexpr int inf32 = 1E9 + 7;
constexpr i64 inf64 = 1E18 + 7;
constexpr int P = 1'000'000'007;
void solve() {
int n;
std::cin >> n;
std::vector<int> d(n);
for (int i = 1, u, v; i < n; ++ i) {
std::cin >> u >> v;
-- u, -- v;
++ d[u], ++ d[v];
}
std::string s;
std::cin >> s;
int c0 = 0, c1 = 0, f = 0, none = 0;
for (int i = 1; i < n; ++ i) {
if (d[i] == 1) {
if (s[i] == '0') ++ c0;
else if(s[i] == '1') ++ c1;
else ++ none;
}
else if (s[i] == '?') {
f ^= 1;
}
}
if (s[0] == '?') {
int res = std::max(c0, c1) + none / 2;
if (f)
res = std::max(res, std::min(c0, c1) + (none + 1) / 2);
std::cout << res << '\n';
}
else {
if (s[0] == '1')
std::cout << c0 + (none + 1) / 2 << '\n';
else
std::cout << c1 + (none + 1) / 2 << '\n';
}
}
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
int t = 1;
std::cin >> t;
while (t--) {
solve();
}
return 0;
}
E - Iris and the Tree
原题链接
思路分析
对于每个path,其收益为 w - 路径外已经确定的边权和
但是要进行特判:如果路径上边全部确定,那么其值就是路径权值和
然后可以注意到每条边在所有路径中一共出现两次
初始时,收益为n * w
最一般的情况:后面每增加一条边,除了该边出现的两条路径,所有的路径的值都会- 2f
如果考虑已经满了的路径,我们发现我们的收益就是:
已满路径的带权长度和 + 未满路径条数 * (w - 已出现路径长度和) + Σ未满路径上已出现的边权和
那么对于一条边<p[u], u>如何找到两条路径的端点?
u - 1 和 u所在子树最大dfs序,第二个我们逆着dfs序进行递推即可
由于给的是dfs序,lca(i, (i + 1) % n) = fa(i + 1),所以我们可以很容易处理出每条路径的边的数目
时间复杂度:O(N)
AC代码
cpp
#include <bits/stdc++.h>
using u32 = unsigned;
using i64 = long long;
using u64 = unsigned long long;
constexpr int inf32 = 1E9 + 7;
constexpr i64 inf64 = 1E18 + 7;
constexpr int P = 1'000'000'007;
void solve() {
int n;
i64 w;
std::cin >> n >> w;
std::vector<int> p(n), d(n), res(n), submax(n);
std::vector<std::vector<int>> adj(n);
for (int i = 1; i < n; ++ i) {
std::cin >> p[i], -- p[i];
adj[p[i]].push_back(i);
}
for (int u = 0; u < n; ++ u)
for (int v : adj[u])
d[v] = d[u] + 1;
for (int i = 0; i + 1 < n; ++ i)
res[i] = d[i] + d[i + 1] - d[p[i + 1]] * 2;
res[n - 1] = d[n - 1];
std::iota(submax.begin(), submax.end(), 0);
for (int i = n - 1; i; -- i)
submax[p[i]] = std::max(submax[i], submax[p[i]]);
std::vector<i64> len(n);
i64 ans = 0, sum = 0, o = 0;
int cnt = 0;
for (int i = 1, v; i < n; ++ i) {
i64 t;
std::cin >> v >> t;
-- v;
int u = v - 1, sub = submax[v];
sum += t;
len[u] += t, len[sub] += t;
o += 2 * t;
if (!-- res[u]) {
ans += len[u];
o -= len[u];
++ cnt;
}
if (! -- res[sub]) {
ans += len[sub];
o -= len[sub];
++ cnt;
}
std::cout << ans + (n - cnt) * (w - sum) + o << " \n"[i + 1 == n];
}
}
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
int t = 1;
std::cin >> t;
while (t--) {
solve();
}
return 0;
}