A - Buy a Pen (abc362 A)
題目大意
給定紅藍綠三支筆的價格,並不買指定顏色的筆,問買一支筆最少需要多少錢。
解題思路
三種情況逐一判斷,取最小即可。
神奇的程式碼
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
int main(void) {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int r, g, b;
string c;
cin >> r >> g >> b >> c;
if (c[0] == 'R') {
r = 999;
} else if (c[0] == 'G') {
g = 999;
} else {
b = 999;
}
cout << min({r, g, b}) << '\n';
return 0;
}
B - Right Triangle (abc362 B)
題目大意
給定三點座標,問是否形成直角三角形。
解題思路
列舉直角點,然後向量點積判斷是否成90度即可。
神奇的程式碼
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
int main(void) {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
array<array<int, 2>, 3> p;
for (auto& x : p)
cin >> x[0] >> x[1];
auto vertical = [](array<int, 2>& a, array<int, 2>& b, array<int, 2>& c) {
return (a[0] - b[0]) * (a[0] - c[0]) + (a[1] - b[1]) * (a[1] - c[1]) ==
0;
};
if (vertical(p[0], p[1], p[2]) || vertical(p[1], p[2], p[0]) ||
vertical(p[2], p[0], p[1])) {
cout << "Yes" << '\n';
} else {
cout << "No" << '\n';
}
return 0;
}
C - Sum = 0 (abc362 C)
題目大意
給定兩個\(n\)個數的陣列 \(l,r\),構造 \(n\)個數 \(x_i\),滿足:
- \(l_i \leq x_i \leq r_i\)
- \(\sum_{i=1}^{n} x_i = 0\)
解題思路
先假定\(x_i = l_i\),此時如果\(\sum_{i=1}^{n} x_i > 0\)則無解。
否則遍歷\(i = 1, 2, 3, ..., n\),對於每個 \(x_i\),依次增大 \(x_i\),直到 \(\sum_{i=1}^{n} x_i = 0\)或者\(x_i = r_i\)。貪心增加即可。
神奇的程式碼
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
int main(void) {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int n;
cin >> n;
vector<int> l(n), r(n);
for (int i = 0; i < n; i++) {
cin >> l[i] >> r[i];
}
vector<int> x = l;
LL sum = accumulate(l.begin(), l.end(), 0ll);
for (int i = 0; i < n; i++) {
if (sum < 0) {
int c = min(-sum, 0ll + r[i] - l[i]);
x[i] += c;
sum += c;
}
}
if (sum != 0) {
cout << "No" << '\n';
} else {
cout << "Yes" << '\n';
for (int i = 0; i < n; i++) {
cout << x[i] << " \n"[i == n - 1];
}
}
return 0;
}
D - Shortest Path 3 (abc362 D)
題目大意
給定一張無向圖,點有點權\(a_i\),邊有邊權\(w_i\),問\(1\)號點到其他點的最短距離。
距離為沿途的所有點的點權和邊的邊權和。
解題思路
就一個樸素的\(dijkstra\)最短路就解決了,轉移的時候邊\(u \to v\)的代價從\(w_i\)改成 \(w_i + a_v\)。
神奇的程式碼
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
int main(void) {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int n, m;
cin >> n >> m;
vector<int> a(n);
for (auto& x : a)
cin >> x;
vector<vector<array<int, 2>>> edge(n);
for (int i = 0; i < m; i++) {
int u, v, w;
cin >> u >> v >> w;
--u, --v;
edge[u].push_back({v, w});
edge[v].push_back({u, w});
}
vector<LL> dis(n, 1e18);
dis[0] = a[0];
priority_queue<pair<LL, int>, vector<pair<LL, int>>, greater<pair<LL, int>>>
team;
team.push({dis[0], 0});
while (!team.empty()) {
auto [d, u] = team.top();
team.pop();
if (dis[u] < d)
continue;
for (auto& [v, w] : edge[u]) {
if (dis[v] > dis[u] + w + a[v]) {
dis[v] = dis[u] + w + a[v];
team.push({dis[v], v});
}
}
}
for (int i = 1; i < n; i++)
cout << dis[i] << " \n"[i == n - 1];
return 0;
}
E - Count Arithmetic Subsequences (abc362 E)
題目大意
給定\(n\)個數 \(a_i\),對於 \(k = 1, 2, ..., n\),問長度為 \(k\)的 \(a\)的子序列中,是等差數列的數量。
解題思路
長度為\(1,2\)的等差數列可以直接算出來,因此考慮長度 \(\geq 3\)的情況。
考慮如何統計等差數列,比如考慮列舉公差,統計不同公差下的子序列數量。
雖然公差的範圍\(< 10^9\),但考慮到公差是兩個數的差,其取值實際只有 \(O(n^2)\)個,而 \(n \leq 80\),可以考慮列舉公差。然後統計該公差下各個長度的子序列數量,累計求和即為答案。
列舉公差 \(d\),剩下就是考慮如何統計其子序列的個數。考慮樸素搜尋,即從左到右依次考慮每個數選或不選,選的話要保證構成公差為 \(d\)的等差數列,需要得知上一個選的數是什麼,同時還要保留我已經選了多少個數。
據此容易想到就是一個樸素的 \(dp\): \(dp[i][k]\)表示 考慮前\(i\)個數,且選擇了第 \(i\)個數,且已經選了 \(k\)個數的等差為 \(d\)的子序列數量。 轉移則列舉\(j\),滿足 \(a_i - a_k = d\),則 \(dp[i][k] += dp[j][k - 1]\)。即\(dp[i][k] = \sum_{a_i - a_j = d} dp[j][k - 1]\)。
列舉公差 \(O(n^2)\), \(dp\)狀態 \(O(n^2)\),轉移 \(O(n)\),總複雜度是 \(O(n^5)\),是無法透過的,考慮最佳化轉移。
對於一個轉移\(dp[i][k] += dp[j][k - 1]\),其中 \(a_i - a_j = d\),遍歷所有的公差時\(d_i\),其實 只有一個\(d_i = d\)時,會發生這個轉移。 也就是說,固定了公差,我們就可以預處理出狀態轉移的前繼狀態,即\(dp[i]\)可以從什麼 \(dp[j]\) 轉移過來,預處理的複雜度是\(O(n^2)\),隨後在求\(dp\)時,轉移就無需遍歷 \(j \in [1,i)\) ,直接遍歷預處理的轉移即可。
這樣預處理之後,求\(dp\)的複雜度是多少呢?因為每個公差預處理出來的 \(i\)的前繼轉移 \(j\)的數量不同,但注意到一個轉移\(dp[i][k] += dp[j][k - 1]\),其中 \(a_i - a_j = d\),遍歷所有的公差時\(d_i\),其實 只有一個\(d_i = d\)時才發生這個轉移,縱觀所有的這類轉移,其數量有\(O(n^2)\)個,但其因為公差 \(d\)被打散在這 \(O(n^2)\)次求 \(dp\)裡,所以所有公差,每個公差遍歷預處理的轉移前繼狀態,總的複雜度是\(O(n^2)\)個狀態+ \(O(n^2)\)次轉移,總的複雜度還是 \(O(n^4)\)。
神奇的程式碼
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
const int mo = 998244353;
int main(void) {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int n;
cin >> n;
vector<int> a(n);
for (auto& x : a)
cin >> x;
vector<int> diff;
for (int i = 0; i < n; i++) {
for (int j = i + 1; j < n; j++) {
diff.push_back(a[j] - a[i]);
}
}
sort(diff.begin(), diff.end());
diff.erase(unique(diff.begin(), diff.end()), diff.end());
vector<int> ans(n + 1, 0);
ans[1] = n;
if (n > 1)
ans[2] = n * (n - 1) / 2;
for (auto d : diff) {
vector<vector<int>> tr(n);
for (int i = 0; i < n; ++i)
for (int j = 0; j < i; ++j)
if (a[i] - a[j] == d)
tr[i].push_back(j);
vector<vector<int>> dp(n, vector<int>(n + 1, 0));
for (int i = 0; i < n; ++i) {
dp[i][1] = 1;
for (int j = 1; j <= i; ++j) {
for (auto& k : tr[i]) {
dp[i][j + 1] += dp[k][j];
if (dp[i][j + 1] >= mo) {
dp[i][j + 1] -= mo;
}
}
}
}
for (int i = 0; i < n; ++i) {
for (int j = 3; j <= n; ++j) {
ans[j] += dp[i][j];
if (ans[j] >= mo) {
ans[j] -= mo;
}
}
}
}
for (int i = 1; i <= n; ++i) {
cout << ans[i] << " \n"[i == n];
}
return 0;
}
F - Perfect Matching on a Tree (abc362 F)
題目大意
給定一棵樹,倆倆配對,收益是兩個點的最短路邊數\(dis(u,v)\)。
構造配對方案,使得收益最大。
解題思路
每次收益是邊數,配對的收益和最大,換個角度考慮貢獻,認為考慮每條邊,其出現在配對最短路的次數。
一條邊將樹拆成兩個連通塊,假設點數分別為\(v_i, n - v_i\),如果配對的兩個點分別在這兩個連通塊裡,這個這條邊對答案就有\(1\)的貢獻 。那一條邊對答案的最大貢獻即為\(min(v_i, n - v_i)\)。
所有邊的最大貢獻和,即為收益的上界,考慮這個上界能否取到。很顯然,對於一些\(v_i\) 很大或很小的,\(min(v_i, n - v_i)\)都比較小,這些邊的貢獻上界很容易取到,因此關鍵要考慮 \(v_i = \frac{n}{2}\) 左右的邊。
而這些邊實際是在樹的重心附近,考慮重心,其特點是最大的兒子數不超過 \(\frac{n}{2}\),重心有好幾個兒子,我們的配對目標是,配對的兩個點來自於不同的兒子子樹。這樣每個兒子子樹裡的邊都可以取到上界。
剩下的問題就是如何選擇兒子子樹。事實上,考慮abc359f,其實構造方法是一樣的。
將每個兒子子樹看成一個點,子樹的點樹視為該點的度數,每取兩個子樹的兒子配對,相當於給這兩個子樹點連邊。然後最終每個點的度數滿足要求。
因此構造方法為,每次選擇一個兒子子樹最大的和非最大的,配對。動態維護子樹大小。
如果點數是奇數,則拋棄重心,否則也把重心視為一個子樹。
神奇的程式碼
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
int main(void) {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int n;
cin >> n;
vector<vector<int>> edge(n);
for (int i = 0; i < n - 1; i++) {
int u, v;
cin >> u >> v;
u--;
v--;
edge[u].push_back(v);
edge[v].push_back(u);
}
vector<int> son(n, 0), weight(n, 0);
int root = 0;
auto dfs = [&](auto&& dfs, int u, int fa) -> void {
son[u] = 1;
for (auto v : edge[u]) {
if (v == fa)
continue;
dfs(dfs, v, u);
son[u] += son[v];
weight[u] = max(weight[u], son[v]);
}
weight[u] = max(weight[u], n - son[u]);
if (weight[u] <= n / 2)
root = u;
};
dfs(dfs, 0, 0);
vector<vector<int>> child;
auto dfs2 = [&](auto&& dfs2, int u, int fa, vector<int>& cc) -> void {
cc.push_back(u);
for (auto v : edge[u]) {
if (v == fa)
continue;
dfs2(dfs2, v, u, cc);
}
};
for (auto& u : edge[root]) {
vector<int> cc;
dfs2(dfs2, u, root, cc);
child.push_back(cc);
}
if (n % 2 == 0)
child.push_back({root});
auto cmp = [&](const int a, const int b) -> bool {
return child[a].size() < child[b].size();
};
priority_queue<int, vector<int>, decltype(cmp)> pq(cmp);
for (int i = 0; i < child.size(); ++i) {
pq.push(i);
}
for (int i = 0; i < n / 2; ++i) {
auto tu = pq.top();
pq.pop();
auto tv = pq.top();
pq.pop();
int u = child[tu].back();
int v = child[tv].back();
child[tu].pop_back();
child[tv].pop_back();
if (child[tu].size() > 0)
pq.push(tu);
if (child[tv].size() > 0)
pq.push(tv);
cout << u + 1 << " " << v + 1 << '\n';
}
return 0;
}
G - Count Substring Query (abc362 G)
題目大意
給定一個字串\(s\),回答 \(q\)個詢問。
每個詢問給定一個字串 \(t\),問字串 \(t\)在字串 \(s\)裡的出現次數。
解題思路
此即為字尾自動機的一個節點的\(|endpos|\)大小,貼個模板即可。
神奇的程式碼
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
class SAM {
enum { len = 1000005, vary = 26 };
int trans[len << 1][vary];
int maxlen[len << 1];
int link[len << 1];
int cnt[len << 1];
int tot;
int last;
public:
SAM() {
tot = last = 0;
link[tot] = -1;
maxlen[tot] = 0;
++tot;
}
void clear() {
for (int i = 0; i < tot; ++i) {
for (int j = 0; j < vary; ++j) {
trans[i][j] = 0;
}
maxlen[i] = link[i] = cnt[i] = 0;
}
tot = last = 0;
link[tot] = -1;
maxlen[tot] = 0;
++tot;
}
void insert(int s) {
int cur = tot++;
maxlen[cur] = maxlen[last] + 1;
int p = last;
for (; p != -1 && !trans[p][s]; p = link[p])
trans[p][s] = cur;
if (p == -1)
link[cur] = 0;
else {
int q = trans[p][s];
if (maxlen[q] == maxlen[p] + 1)
link[cur] = q;
else {
int clone = tot++;
maxlen[clone] = maxlen[p] + 1;
link[clone] = link[q];
for (int i = 0; i < vary; ++i)
trans[clone][i] = trans[q][i];
for (; p != -1 && trans[p][s] == q; p = link[p])
trans[p][s] = clone;
link[q] = link[cur] = clone;
}
}
cnt[cur] = 1;
last = cur;
}
int tong[len];
int sa[len << 1];
void build() {
tong[0] = 0;
for (int i = 1; i < tot; ++i)
++tong[maxlen[i]];
for (int i = 1; i < len; ++i)
tong[i] += tong[i - 1];
for (int i = 1; i < tot; ++i)
sa[tong[maxlen[i]]--] = i;
for (int i = tot - 1; i >= 0; --i) {
int p = sa[i];
cnt[link[p]] += cnt[p];
}
}
int solve(string& t) {
int cur = 0;
for (auto c : t) {
auto s = c - 'a';
if (trans[cur][s] == 0)
return 0;
cur = trans[cur][s];
}
return cnt[cur];
}
} sam;
int main(void) {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
string s;
cin >> s;
for (auto c : s)
sam.insert(c - 'a');
sam.build();
int q;
cin >> q;
while (q--) {
string t;
cin >> t;
int ans = sam.solve(t);
cout << ans << '\n';
}
return 0;
}