比赛链接:https://codeforces.com/contest/1529
A. Eshag Loves Big Arrays
题解
反复选取最小值和大于它的数即可。
代码
#include <bits/stdc++.h>
using namespace std;
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int t;
cin >> t;
while (t--) {
int n;
cin >> n;
vector<int> a(n);
for (int i = 0; i < n; i++) {
cin >> a[i];
}
cout << n - count(a.begin(), a.end(), *min_element(a.begin(), a.end())) << "
";
}
return 0;
}
B. Sifid and Strange Subsequences
题解
因为要求任意两对都大于等于最大值,所以排序找相邻最小值,然后枚举当前值能否为最大值即可。
代码
#include <bits/stdc++.h>
using namespace std;
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int t;
cin >> t;
while (t--) {
int n;
cin >> n;
vector<int> a(n);
for (int i = 0; i < n; i++) {
cin >> a[i];
}
sort(a.begin(), a.end());
int mi = INT_MAX, ans = 0;
for (int i = 0; i < n; i++) {
if (i > 0) {
mi = min(mi, a[i] - a[i - 1]);
}
if (mi >= a[i]) {
ans = i + 1;
}
}
cout << ans << "
";
}
return 0;
}
C. Parsa's Humongous Tree
题解
每个结点取 (l_i) 或 (r_i) 收益最大,树形 (dp) 。
证明
假设一个结点取 ((l_i, r_i)) 间的某值,设与它相邻的结点中小于此值的有 (p) 个,大于此值的有 (q) 个,根据 (p, q) 的大小关系可以分为以下三种情况:
- (p < q) ,此时取值向 (l_i) 靠近收益一定增加
- (p > q) ,此时取值向 (r_i) 靠近收益一定增加
- (p = q) ,此时取值向 (l_i) 或 (r_i) 靠近收益一定不会减少
所以,每个结点只取 (l_i) 或 (r_i) 即可。
代码
#include <bits/stdc++.h>
using namespace std;
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int t;
cin >> t;
while (t--) {
int n;
cin >> n;
vector<int> l(n), r(n);
for (int i = 0; i < n; i++) {
cin >> l[i] >> r[i];
}
vector<vector<int>> G(n);
for (int i = 0; i < n - 1; i++) {
int u, v;
cin >> u >> v;
--u, --v;
G[u].push_back(v);
G[v].push_back(u);
}
vector<vector<long long>> dp(n, vector<long long> (2));
function<void(int, int)> dfs = [&](int u, int p) {
for (auto v : G[u]) {
if (v == p) {
continue;
}
dfs(v, u);
dp[u][0] += max(dp[v][0] + abs(l[u] - l[v]), dp[v][1] + abs(l[u] - r[v]));
dp[u][1] += max(dp[v][0] + abs(r[u] - l[v]), dp[v][1] + abs(r[u] - r[v]));
}
};
dfs(0, -1);
cout << max(dp[0][0], dp[0][1]) << "
";
}
return 0;
}
D. Kavi on Pairing Duty
题解
假设点 (1) 与 (x) 配对,根据 (x) 与 (n) 的大小关系可以分为以下两种情况:
(x > n) :
-
此时 (x + 1) 由于不被 ([1,x]) 包含,只能与 (2) 配对。
-
同理, ([x, 2n]) 间的点只能依次与 ([1, 1 + 2n - x]) 配对。
-
此时在 ((1 + 2n - x, x)) 中还有 (2(x - n - 1)) 个点,即余下的点总为 (2) 的倍数,所以可以设 (dp_i) 为 (2i) 个点时的方案数。
-
当 (x) 取大于 (n) 的 (n+1, n+2,dots,2n) 时,余下的点依次为 (2 imes (0,1,dots,n-1)) 个,由于这些点被所有区间包含,所以只需考虑它们自身即可。即,当 (x > n) 时,总的方案数之和为 (sum limits _{i = 0}^{n - 1} dp_i) 。
(x le n) :
-
此时 (x + 1) 可以与 (2) 或 (2x) 配对。若 (x + 1) 与 (2x) 配对,由于 (2) 不被 ([x + 1, 2x]) 包含所以必须与 (x + 1) 配对,矛盾,所以 (x + 1) 仍只能与 (2) 配对。
-
同理, ([x, 2(x - 1)]) 间的点只能依次与 ([1, x - 1]) 配对。
-
此时 (2n) 个点被分割成了一个个长为 (2(x - 1)) 的小整体,所以 (x - 1) 需要整除 (n) ,又由于 (x - 1 le n - 1) ,所以共有 (n) 的真因子个数种分法。即 (x le n) 的情况共有 (div_n) 种分法。
综上,推得 (dp_n = div_n + sum limits _{i = 0}^{n - 1} dp_i) 。
代码
#include <bits/stdc++.h>
using namespace std;
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
constexpr int N = 1e6 + 10;
vector<int> div(N);
for (int i = 1; i < N; i++) {
for (int j = i + i; j < N; j += i) {
++div[j];
}
}
constexpr int MOD = 998244353;
vector<int> dp(N);
int sum = dp[0] = 1;
for (int i = 1; i < N; i++) {
dp[i] = (div[i] + sum) % MOD;
sum = (sum + dp[i]) % MOD;
}
int n;
cin >> n;
cout << dp[n] << "
";
return 0;
}
E. Trees of Tranquillity
题解
由于构造图为完全图,所以其中的点在树一中两两存在父子关系,即在树一中为一条链,所以可以用 (dfs) 回溯遍历树一中的链。
为了快速判断两点在树二上是否存在父子关系,可以用 (dfs) 序打时间戳,若 (v) 为 (u) 的子代,则一定有 (in_u < in_v < out_u) ,同时,由于贪心策略,若两点在树二中存在父子关系,取子结点更优。
代码
#include <bits/stdc++.h>
using namespace std;
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int t;
cin >> t;
while (t--) {
int n;
cin >> n;
vector<vector<int>> G1(n);
for (int v = 1; v < n; v++) {
int u;
cin >> u;
--u;
G1[u].push_back(v);
}
vector<vector<int>> G2(n);
for (int v = 1; v < n; v++) {
int u;
cin >> u;
--u;
G2[u].push_back(v);
}
vector<int> in(n), out(n), id(n);
int timeStamp = 0;
function<void(int)> dfsG2 = [&](int u) {
in[u] = timeStamp++;
id[in[u]] = u;
for (auto v : G2[u]) {
dfsG2(v);
}
out[u] = timeStamp;
};
dfsG2(0);
int ans = 0;
set<int> st;
function<void(int)> dfsG1 = [&](int u) {
bool add = false;
int del = -1;
auto it = st.upper_bound(in[u]);
if (it != st.end() and *it < out[u]) { //若 set 中已有子结点,由贪心策略,取子结点更优
} else {
if (it != st.begin()) {
--it;
if (out[id[*it]] > in[u]) { //同上, erase 掉父结点
del = *it;
st.erase(it);
}
}
st.insert(in[u]);
add = true;
}
ans = max(ans, (int)st.size());
for (auto v : G1[u]) {
dfsG1(v);
}
if (add) {
st.erase(in[u]);
if (del != -1) {
st.insert(del);
}
}
};
dfsG1(0);
cout << ans << "
";
}
return 0;
}
F. It's a bird! No, it's a plane! No, it's AaParsa!
题解
本质上还是最短路问题,只不过多出了每个点等待的时间。
由于点的个数 (n) 远少于边数 (m) ,所以采用 (O_{(n^2)}) 的 (Dijkstra) 。
代码
#include <bits/stdc++.h>
using namespace std;
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n, m;
cin >> n >> m;
vector<vector<pair<int, int>>> G(n);
for (int i = 0; i < m; i++) {
int u, v, w;
cin >> u >> v >> w;
G[u].emplace_back(v, w);
}
const int INF = 1e9 + n;
for (int s = 0; s < n; s++) {
vector<int> dis(n, INF);
vector<bool> vis(n);
for (auto [v, w] : G[s]) {
dis[v] = w;
}
for (int loop = 0; loop < n; loop++) {
int u = -1;
for (int i = 0; i < n; i++) {
if (vis[i]) {
continue;
}
if (u == -1 or dis[i] < dis[u]) {
u = i;
}
}
vis[u] = true;
for (int wait = 0; wait < n; wait++) {
dis[(u + wait) % n] = min(dis[(u + wait) % n], dis[u] + wait);
} //其实只考虑每个点等 1 秒的情况即可,和等 n 秒的情况是等价的
for (auto [v, w] : G[u]) {
dis[(v + dis[u]) % n] = min(dis[(v + dis[u]) % n], w + dis[u]);
}
}
dis[s] = 0;
for (int i = 0; i < n; i++) {
cout << dis[i] << "
"[i == n - 1];
}
}
return 0;
}