省流版
- A. 暴力即可
- B. 求出字母位置,絕對值相加即可
- C. 顯然答案為兩個陣列的最大值的和
- D. 注意直接BFS的點權範圍不超過題目範圍,直接BFS即可
- E. 發現單調性,二分票數,用字首和\(O(1)\)判斷可行性即可
- F. 樸素揹包DP,相同重量的物品一起考慮,用優先佇列求解\(l\)個相同重量物品最大價值的最優取法即可
A - September (abc373 A)
題目大意
給定\(12\)個字串,問第 \(i\)個字串的長度是不是 \(i\)。
解題思路
按照題意依次判斷即可,python
可以一行。
神奇的程式碼
print(sum(i + 1 == len(input().strip()) for i in range(12)))
B - 1D Keyboard (abc373 B)
題目大意
給定一個鍵盤,從左到右的\(26\)個字母分別是什麼。
現在依次輸入abcdefgh...xyz
,初始手指在a
處,然後要移動到b
處按b
,然後移動到c
處按c
...
問輸入完這\(26\)個字元需要移動的距離數。
解題思路
當前手指在位置\(x\),移動到下一個字母的位置 \(y\) ,距離數是\(|x-y|\),求出 \(pos[i]\)表示字元 \(i\)的位置,答案就是 \(|pos[i] - pos[i-1]|\)的累加。
神奇的程式碼
#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);
string s;
cin >> s;
array<int, 26> pos{};
for (int i = 0; i < 26; ++i)
pos[s[i] - 'A'] = i;
int ans = 0;
for (int i = 1; i < 26; ++i)
ans += abs(pos[i] - pos[i - 1]);
cout << ans << '\n';
return 0;
}
C - Max Ai+Bj (abc373 C)
題目大意
給定兩個陣列\(a,b\),分別從中選一個數,使得和最大。
解題思路
顯然選的兩個數分別為最大的數即可。python
可以兩行。
神奇的程式碼
input()
print(max(map(int, input().split())) + max(map(int, input().split())))
D - Hidden Weights (abc373 D)
題目大意
給定一張有向圖,邊有邊權。
確定點權\(x_i\),滿足對於每一條有向邊 \(u_i \to v_i, w_i\),滿足 \(x_{v_i} - x_{u_i} = w_i\)。
題目保證有解。
解題思路
視為無向圖,但反過來的邊權是\(-w_i\),然後從未訪問過的點開始\(BFS\),其點權為\(0\),然後按照上述等式得到周圍點的點權即可。
點數\(n \leq 2 \times 10^5\),邊權 \(\leq 10^9\),所以點權不會超過\(2 \times 10^{14}\),滿足題意範圍的 \(10^{18}\)。
神奇的程式碼
#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<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> ans(n);
queue<int> team;
vector<int> vis(n);
auto BFS = [&](int st) {
team.push(st);
vis[st] = 1;
while (!team.empty()) {
int u = team.front();
team.pop();
for (auto& [v, w] : edge[u]) {
if (vis[v])
continue;
ans[v] = ans[u] + w;
vis[v] = 1;
team.push(v);
}
}
};
for (int i = 0; i < n; ++i)
if (!vis[i])
BFS(i);
for (int i = 0; i < n; ++i)
cout << ans[i] << " \n"[i == n - 1];
return 0;
}
E - How to Win the Election (abc373 E)
題目大意
\(n\)個候選人, \(k\)張票。最終票數最多的\(m\)個候選人獲勝,同票數的都會獲勝,因此可能獲勝的可能會超過 \(m\)個 。
現已知部分投票情況。 問每一個候選人,需要給他至少多少張票,才能使得他一定會獲勝,即無論剩餘票數如何分配,他始終都是票數前\(m\)多的。
解題思路
對於當前第\(i\)個候選人,他的票數\(v_i\)目前排第 \(rank_i\)名,那他至少還要多少票才能穩贏呢?
最樸素的想法就是,列舉這個票數,即假設他得到了\(x\)票,看看能否穩贏? (如果你沒想到二分,可能是因為這個最樸素的做法沒想到——透過增設條件來解決問題,從這個條件其實是很容易看出來具有單調性,進而可以二分)
那能否穩贏呢?假設他得到了\(x\)票,那此時的票數是 \(v_i + x\),透過二分可以得到該票數的排名,假設是\(r_i\)名。然後還剩下\(left\)張票沒投。
- 如果 \(r_i > m\),那他必不可能贏了。
- 如果 \(r_i \leq m\),即此時他前面只有 \(r_i - 1\)個人,剩下的票分配給其他人,只要少於 \(danger = m - r_i + 1\)個人的票數超過他,那他就能贏。
考慮最壞情況,即為票數\(\leq v_i + x\)的最大的\(danger\)個人的票數達到\(v_i + x + 1\)所需要的票數,少於剩餘未投的票數 \(left\),那第 \(i\)個人一定是票數前 \(m\) 大的,即穩贏。而前者的票數計算相當於是\(danger \times (v_i + x + 1) - \sum v_j\) ,事先對\(v\)排序,後者其實就是一個區間和,可以用字首和最佳化,在 \(O(1)\)的時間內得到。
到此,我們可以 \(O(1)\)判斷當前列舉的票數 \(x\)是不是穩贏的。但列舉的票數的範圍有 \(O(k)\),但容易發現該票數
與是否穩贏
之間具有單調性——給越多票,穩贏的可能性越高。
因此我們不必一個一個列舉票數,而是二分該票數,然後 透過字首和,\(O(1)\) 判斷是否可行即可。
對所有人都這麼求一遍,總的時間複雜度就是\(O(n \log k)\)。
上述驗證的想法其實很樸素,考慮最壞情況,就看票數小於他的那麼些人能否趕超。所需票數的計算可以用字首和最佳化。
神奇的程式碼
#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;
LL k;
cin >> n >> m >> k;
vector<LL> a(n);
for (auto& i : a)
cin >> i;
vector<int> id(n);
iota(id.begin(), id.end(), 0);
sort(id.begin(), id.end(), [&](int i, int j) { return a[i] > a[j]; });
vector<LL> sum(n);
sum[0] = a[id[0]];
for (int i = 1; i < n; ++i)
sum[i] = sum[i - 1] + a[id[i]];
vector<LL> ans(n);
LL left = k - accumulate(a.begin(), a.end(), 0ll);
auto get_sum = [&](int l, int r, int ex) {
if (l > r)
return 0ll;
LL s = 0;
if (l <= ex && ex <= r) {
r += 1;
s -= a[id[ex]];
}
s += sum[r] - (l ? sum[l - 1] : 0);
return s;
};
auto solve = [&](int pos, int rank) {
auto check = [&](LL votes) {
LL now_votes = a[pos] + votes;
int now_rank = lower_bound(id.begin(), id.end(), now_votes,
[&](int x, LL v) { return a[x] > v; }) -
id.begin();
int left_candi = m - now_rank;
LL demand = left_candi * (now_votes + 1) -
get_sum(now_rank, now_rank + left_candi - 1, rank);
LL ano = left - votes;
return ano < demand;
};
LL l = -1, r = left;
while (l + 1 < r) { //(l, r]
LL mid = (l + r) >> 1;
if (check(mid))
r = mid;
else
l = mid;
}
return check(r) ? r : -1;
};
for (int i = 0; i < n; ++i) {
ans[id[i]] = solve(id[i], i);
}
for (int i = 0; i < n; ++i)
cout << ans[i] << " \n"[i == n - 1];
return 0;
}
F - Knapsack with Diminishing Values (abc373 F)
題目大意
\(n\)種物品,重量 \(w_i\),價值\(v_i\),無限個。
揹包容量 \(W\),現在選物品放入揹包,不超揹包容量,價值最大。
當第 \(i\)種物品放 \(k\)個時,其價值為 \(kv_i - k^2\)。
解題思路
考慮最樸素的揹包做法,即\(dp[i][j]\)表示考慮前 \(i\)個物品,放的容量是 \(j\)的最大價值。轉移即考慮當前物品放多少個,其時間複雜度為 \(O(nw^2)\)。此處 \(n \leq 3000\),無法透過。
究其複雜度,主要在考慮每種物品,其重量是\(w_i\),我們需要考慮該物品的數量為 \(1,2,...,\lfloor \frac{W}{w_i} \rfloor\) 。如果每個物品的重量都挺小的,那麼物品數量的數量級就是\(O(W)\)。轉移的複雜度就是 \(O(W)\)。
怎麼辦呢?對於重量都小的,我們能否一起考慮呢?
即我們不依次考慮每種物品,而是依次考慮重量為\(i\)的所有物品,即設 \(dp[i][j]\)表示考慮重量\(\leq i\)的物品,放的容量是 \(j\)的最大價值。這樣考慮的物品數量就是\(O(\frac{W}{i})\),對所有的\(i\)的轉移複雜度累加,其複雜度就是 \(O(w^2 \log w)\)。
複雜度對了,考慮如何轉移呢,即\(dp[i][j]\),然後列舉選重量為 \(i\)的物品的數量個數 \(l\),假設其物品的價值分別是 \(v_1,v_2,v_3,...\),現在要取 \(l\)個,怎麼取最優?
由於每種物品的價值,隨著取的個數的增加,其價值會減少,最樸素的想法就是一個一個取。
考慮每種物品是一行,從左到右的物品的價值依次減少,即設\(f_i(k) = kv_i - k^2\),第一行第一個物品的價值是\(f_1(1)\),第二個物品的價值就是\(f_1(2) - f_1(1)\),第三個物品的價值就是 \(f_1(3) - f_1(2)\),而第二行的第一個物品的價值就是\(f_2(1)\)。
上述怎麼取的問題就變成,每行取一個字首的物品,一共 \(l\)個,價值最大。
我們就考慮每行的第一個未取的物品,每次就取這些行裡,價值最大的。如何快速找到這些的最大值,用優先佇列維護即可,佇列裡的元素最多也就\(O(n)\)。
由於取物品數量\(l\)是從 \(1\)開始列舉的,因此求最優的取法就這樣依次迭代,從優先佇列裡取價值最大的物品即可。
總的時間複雜度是 \(O(w^2 \log w \log n)\)
神奇的程式碼
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
const LL inf = 1e18;
int main(void) {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int n, w;
cin >> n >> w;
vector<vector<int>> a(w + 1);
for (int i = 0; i < n; i++) {
int w, v;
cin >> w >> v;
a[w].push_back(v);
}
vector<LL> dp(w + 1, -inf);
dp[0] = 0;
auto f = [&](int k, int v) { return 1ll * k * v - k * k; };
auto get_val = [&](int k, int v) { return f(k, v) - f(k - 1, v); };
for (int i = 0; i <= w; i++) {
if (!a[i].empty()) {
vector<LL> dp2 = dp;
for (int j = 0; j <= w; j++) {
priority_queue<pair<LL, pair<int, int>>> q;
for (auto x : a[i]) {
q.push({get_val(1, x), {1, x}});
}
LL sum = 0;
for (int l = i; j + l <= w; l += i) {
auto [val, p] = q.top();
auto [k, v] = p;
q.pop();
sum += val;
dp2[j + l] = max(dp2[j + l], dp[j] + sum);
q.push({get_val(k + 1, v), {k + 1, v}});
}
}
dp.swap(dp2);
}
}
LL ans = *max_element(dp.begin(), dp.end());
cout << ans << '\n';
return 0;
}
G - No Cross Matching (abc373 G)
題目大意
二維平面,給定兩組點\(p,q\)各\(n\)個,打亂 \(q\)的順序,使得\(n\)個線段 \(p_i \to q_i\)互不相交。
給定一種 \(q\)的順序或告知不可能。
解題思路
<++>
神奇的程式碼