Codeforces Round #722 (Div. 2)
比赛链接: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())) << "\n";
}
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 << "\n";
}
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]) << "\n";
}
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 \times (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] << "\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 << "\n";
}
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] << " \n"[i == n - 1];
}
}
return 0;
}