A. Buy a Pen

模拟

代码实现
a = list(map(int, input().split()))
c = input()
a.pop(['Red', 'Green', 'Blue'].index(c))
print(min(a))

B. Right Triangle

一个角为直角等价于这个夹角的两边对应的向量的内积等于 0

代码实现
#include <bits/stdc++.h>
#define rep(i, n) for (int i = 0; i < (n); ++i)
using namespace std;
struct Vector {
int x, y;
Vector(int x=0, int y=0): x(x), y(y) {}
Vector operator-(const Vector& o) const {
return Vector(x-o.x, y-o.y);
}
int dot(const Vector& v) {
return x*v.x + y*v.y;
}
};
int main() {
Vector a, b, c;
cin >> a.x >> a.y;
cin >> b.x >> b.y;
cin >> c.x >> c.y;
bool ok = false;
rep(i, 3) {
if ((b-a).dot(c-a) == 0) ok = true;
swap(a, b); swap(b, c);
}
if (ok) puts("Yes");
else puts("No");
return 0;
}

C. Sum = 0

显然最小值可以取到 Li,最大值可以取到 Ri
可以取到 0 当且仅当最小值 0 且最大值 0

下面考虑如何构造一个合法解

不妨先将每个 xi 初始化成 Li,然后考虑将总的可分配的增量 Li 加给每个 xi,我们可以让前面的数加到 Ri,如果剩余可分配的增量不够加到 Ri 的话,就直接将剩余的增量加到当前 xi 上并结束遍历。

好像这个技巧很常见?

代码实现
#include <bits/stdc++.h>
#define rep(i, n) for (int i = 0; i < (n); ++i)
using namespace std;
using ll = long long;
int main() {
int n;
cin >> n;
vector<int> l(n), r(n);
rep(i, n) cin >> l[i] >> r[i];
ll sumL = 0, sumR = 0;
rep(i, n) sumL += l[i];
rep(i, n) sumR += r[i];
if (sumL > 0 or sumR < 0) {
puts("No");
return 0;
}
puts("Yes");
vector<int> ans = l;
ll rem = -sumL;
rep(i, n) {
ll canAdd = r[i]-l[i];
if (canAdd < rem) {
ans[i] = r[i];
rem -= canAdd;
}
else {
ans[i] += rem;
break;
}
}
rep(i, n) cout << ans[i] << ' ';
return 0;
}

D. Shortest Path 3

Dijkstra 跑最短路的板题

代码实现
#include <bits/stdc++.h>
#define rep(i, n) for (int i = 0; i < (n); ++i)
using namespace std;
using ll = long long;
using P = pair<ll, int>;
struct Edge {
int to, cost;
Edge(int to, int cost): to(to), cost(cost) {}
};
int main() {
int n, m;
cin >> n >> m;
vector<int> a(n);
rep(i, n) cin >> a[i];
vector<vector<Edge>> g(n);
rep(i, m) {
int u, v, b;
cin >> u >> v >> b;
--u; --v;
g[u].emplace_back(v, b+a[v]);
g[v].emplace_back(u, b+a[u]);
}
const ll INF = 1e18;
vector<ll> dist(n, INF);
priority_queue<P, vector<P>, greater<P>> q;
dist[0] = a[0]; q.emplace(a[0], 0);
while (q.size()) {
auto [d, v] = q.top(); q.pop();
if (dist[v] != d) continue;
for (auto [u, w] : g[v]) {
ll nd = d+w;
if (dist[u] <= nd) continue;
dist[u] = nd;
q.emplace(nd, u);
}
}
for (int i = 1; i < n; ++i) cout << dist[i] << ' ';
return 0;
}

E. Count Arithmetic Subsequences

dp[i][j][k] 表示在前 i 个数中选 k 个数且最后一个数选 ai 且倒数第 2 个数选 aj 的合法方案数
状态数为 O(N3),转移次数为 O(N),这里 N 比较小所以是可行的

代码实现
#include <bits/stdc++.h>
#if __has_include(<atcoder/all>)
#include <atcoder/all>
using namespace atcoder;
#endif
#define rep(i, n) for (int i = 0; i < (n); ++i)
using namespace std;
using mint = modint998244353;
mint dp[81][81][81];
int main() {
int n;
cin >> n;
vector<int> a(n);
rep(i, n) cin >> a[i];
rep(i, n) {
rep(j, i) dp[i][j][2] = 1;
rep(j, i)rep(k, n) {
mint now = dp[i][j][k];
if (now == 0) continue;
for (int x = i+1; x < n; ++x) {
if (a[x]-a[i] == a[i]-a[j]) {
dp[x][i][k+1] += now;
}
}
}
}
vector<mint> ans(n+1);
ans[1] = n;
for (int k = 2; k <= n; ++k) {
rep(i, n)rep(j, i) ans[k] += dp[i][j][k];
}
for (int i = 1; i <= n; ++i) cout << ans[i].val() << ' ';
return 0;
}

也有另一种dp做法
dp[i][j][d] 表示在前 i 个数中选 j 个数且最后一个数选 ai 使得公差为 d 的合法方案数
其中公差 d 可以用 std::unordered_map 来维护
时间复杂度为 O(N3)

代码实现
#include <bits/stdc++.h>
#if __has_include(<atcoder/all>)
#include <atcoder/all>
using namespace atcoder;
#endif
#define rep(i, n) for (int i = 0; i < (n); ++i)
using namespace std;
using mint = modint998244353;
unordered_map<int, mint> dp[81][81];
int main() {
int n;
cin >> n;
vector<int> a(n);
rep(i, n) cin >> a[i];
rep(ni, n)rep(i, ni) {
int d = a[ni]-a[i];
dp[ni][2][d] += 1;
rep(j, n) dp[ni][j+1][d] += dp[i][j][d];
}
vector<mint> ans(n+1);
ans[1] = n;
for (int j = 2; j <= n; ++j) {
rep(i, n) {
for (auto [d, x] : dp[i][j]) ans[j] += x;
}
}
for (int i = 1; i <= n; ++i) cout << ans[i].val() << ' ';
return 0;
}

F. Perfect Matching on a Tree

注意到,对于每条边可贡献的次数的上界可以取到较小子树的大小
那么,我们只需找到树的重心,然后让每个点穿过重心进行匹配即可
特别地,如果 n 是奇数的话,那就不匹配重心

代码实现
#include <bits/stdc++.h>
#define rep(i, n) for (int i = 0; i < (n); ++i)
using namespace std;
int main() {
int n;
cin >> n;
vector<vector<int>> to(n);
rep(i, n-1) {
int a, b;
cin >> a >> b;
--a; --b;
to[a].push_back(b);
to[b].push_back(a);
}
int centroid = 0;
vector<int> sz(n);
auto dfs = [&](auto& f, int v, int p=-1) -> int {
int mx = 0;
sz[v] = 1;
for (int u : to[v]) {
if (u == p) continue;
sz[v] += f(f, u, v);
mx = max(mx, sz[u]);
}
mx = max(mx, n-sz[v]);
if (mx*2 <= n) centroid = v;
return sz[v];
};
dfs(dfs, 0);
vector<int> vs;
auto dfs2 = [&](auto& f, int v, int p=-1) -> void {
for (int u : to[v]) {
if (u == p) continue;
f(f, u, v);
}
vs.push_back(v);
};
dfs2(dfs2, centroid);
if (n%2 == 1) vs.pop_back();
rep(i, n/2) {
int a = vs[i], b = vs[i+n/2];
cout << a+1 << ' ' << b+1 << '\n';
}
return 0;
}

G. Count Substring Query

板题,几乎在任何oj上都有?
对于每个询问就是求对于 S 的后缀 S1,S2,,SN,从中找出有多少个后缀满足 T 是它的前缀。考虑在 T 后面加一个新的字符 ~,得到 T,原问题等价于求满足 TSiTi 的个数。
关于后缀数组的做法,就是二分出在 S 的所有后缀中满足 SkT 的最小的排名 k,快速询问排名对应的后缀的下标就是后缀数组的强项了
就不贴代码了