A - Penalty Kick (abc348 A)
題目大意
給定\(n\),輸出 \(ooxooxoox...\),長度為 \(n\)。
解題思路
按題意模擬即可。
神奇的程式碼
n = int(input())
ans = "oox" * (n // 3) + "o" * (n % 3)
print(ans)
B - Farthest Point (abc348 B)
題目大意
給定\(n\)個點,對每個點,求出與其距離最遠的點的下標。
解題思路
\(n\)只有 \(100\),對於每個點,花 \(O(n)\)遍歷每個點最大化距離,時間複雜度為 \(O(n^2)\)。
神奇的程式碼
#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<array<int, 2>> p(n);
for (auto& i : p)
cin >> i[0] >> i[1];
for (int i = 0; i < n; ++i) {
int dis = 0, ans = 0;
for (int j = 0; j < n; ++j) {
if (i == j)
continue;
int dis1 = (p[i][0] - p[j][0]) * (p[i][0] - p[j][0]) +
(p[i][1] - p[j][1]) * (p[i][1] - p[j][1]);
if (dis1 > dis) {
dis = dis1;
ans = j;
} else if (dis1 == dis) {
ans = min(ans, j);
}
}
cout << ans + 1 << '\n';
}
return 0;
}
C - Colorful Beans (abc348 C)
題目大意
\(n\)個豌豆,每個豌豆有美味值 \(a\)和顏色值 \(c\)。
豌豆之間只有顏色區別。
現在你會從一種顏色的豌豆裡選一個豌豆出來。
最大化選出來的豌豆美味值的最小值。
解題思路
選定一個顏色,最壞情況就是選出了這個顏色中美味值最小的豌豆。
因此對每種顏色求出美味值最小的豌豆,然後所有顏色(決策)的美味值取個最大值即為答案。
神奇的程式碼
#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;
map<int, int> minn;
for (int i = 0; i < n; i++) {
int a, c;
cin >> a >> c;
if (minn.find(c) == minn.end()) {
minn[c] = a;
} else
minn[c] = min(minn[c], a);
}
int ans = minn.begin()->second;
for (auto& [c, a] : minn) {
ans = max(ans, a);
}
cout << ans << '\n';
return 0;
}
D - Medicines on Grid (abc348 D)
題目大意
二維網格,起點終點,有障礙物,有藥,藥的作用是將體力值變為\(c_i\)。
初始起點,體力值為 \(0\),問能否走到終點。
解題思路
考慮樸素搜尋,記錄狀態\((i,j,k)\),表示位於 \((i,j)\),當前體力值為 \(k\),然後列舉四個方向搜尋後繼情況。
考慮總情況數,即 \(O(200^2 \times 200^2)\),有點危險,可以加一些剪枝,比如設 \(dis[i][j]\)表示到達 \((i,j)\)時的最大體力,採用 \(SPFA\)式的搜尋即可。
神奇的程式碼
#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 h, w;
cin >> h >> w;
vector<string> tu(h);
for (auto& i : tu)
cin >> i;
vector<vector<int>> dp(h, vector<int>(w, -1));
vector<vector<int>> med(h, vector<int>(w, -1));
int n;
cin >> n;
for (int i = 0; i < n; i++) {
int r, c, e;
cin >> r >> c >> e;
--r, --c;
med[r][c] = e;
}
queue<pair<int, int>> q;
vector<vector<int>> inq(h, vector<int>(w, 0));
for (int i = 0; i < h; i++) {
for (int j = 0; j < w; j++) {
if (tu[i][j] == 'S') {
dp[i][j] = med[i][j];
q.push({i, j});
inq[i][j] = 1;
}
}
}
const array<int, 4> dx = {0, 0, 1, -1};
const array<int, 4> dy = {1, -1, 0, 0};
while (!q.empty()) {
auto [x, y] = q.front();
q.pop();
inq[x][y] = 0;
if (dp[x][y] <= 0)
continue;
for (int i = 0; i < 4; i++) {
int nx = x + dx[i], ny = y + dy[i];
if (nx < 0 || nx >= h || ny < 0 || ny >= w || tu[nx][ny] == '#')
continue;
if (dp[nx][ny] < dp[x][y] - 1) {
dp[nx][ny] = max(dp[x][y] - 1, med[nx][ny]);
if (tu[nx][ny] == 'T')
break;
if (!inq[nx][ny]) {
q.push({nx, ny});
inq[nx][ny] = 1;
}
}
}
}
int ok = false;
for (int i = 0; i < h; i++) {
for (int j = 0; j < w; j++) {
if (tu[i][j] == 'T' && dp[i][j] >= 0) {
ok = true;
break;
}
}
}
if (ok) {
cout << "Yes\n";
} else {
cout << "No\n";
}
return 0;
}
E - Minimize Sum of Distances (abc348 E)
題目大意
給定一棵樹,點有點權\(c_i\),邊權為\(1\)。
定義 \(f(x) = \sum_{i}c_i dis(x, i)\),其中\(dis(x,i)\)表示點 \(x\)到點 \(i\)的最小距離。
求\(f(x)\)的最小值。
解題思路
樸素求法即\(O(n^2)\)。列舉每個點\(x\),然後求一遍 \(dis(x,i)\) ,然後計算\(f(x)\),取最小值。列舉每個點耗時 \(O(n)\),計算 \(f(x)\)耗時 \(O(n)\)。
考慮最佳化 \(f(x)\)的計算。注意到是棵樹,我們一般考慮先列舉根 \(0\),計算 \(f(0)\),然後考慮列舉 \(0\)的兒子 \(x\),看看 \(f(x)\)能否從 \(f(0)\)得到。
列舉的點從 \(0 \to x\),考慮\(f(0) \to f(x)\)的計算變化,只有\(dis(0,i)\)變成 \(dis(x,i)\), 其中\(i \in x\)的子樹的 \(dis(x,i) = dis(0,i) - 1\),其餘的 \(i\)的 \(dis(x, i) = dis(0,i) + 1\)。
即 \(f(0) = \sum_{i}c_i dis(0, i)\),這是我們已經算出來的。
當\(0 \to x\)時,\(f(x) = \sum_{i}c_i dis(x, i) = \sum_{i \in x\text{子樹}}c_i (dis(0, i) - 1) + \sum_{i \notin x\text{子樹}}c_i (dis(0, i) + 1) = \sum_{i}c_i dis(0, i) - \sum_{i \in x\text{子樹}} c_i + \sum_{i \notin x\text{子樹}}c_i\)
代入\(f(0) = \sum_{i}c_i dis(0, i)\)得,\(f(x)= f(0) - \sum_{i \in x\text{子樹}} c_i + \sum_{i \notin x\text{子樹}}c_i\)
因此我們預處理\(sum_x\)表示以 \(x\)為子樹的 \(c_i\)的和, \(sum\)是所有的 \(c_i\)的和,那不在 \(x\)的子樹的 \(c_i\)的和可以表示成 \(sum - sum_x\)。這樣當 \(0 \to x\)時, \(f(x)\)就可以透過 \(f(0),sum_x,sum\)在 \(O(1)\)內算出來了。
總的時間複雜度變為 \(O(n)\)。
神奇的程式碼
#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> c(n);
for (auto& x : c)
cin >> x;
LL sum = accumulate(c.begin(), c.end(), 0ll);
vector<LL> csum(n);
LL f = 0;
auto dfs = [&](auto self, int u, int fa, int deep) -> void {
for (auto v : edge[u]) {
if (v == fa)
continue;
self(self, v, u, deep + 1);
csum[u] += csum[v];
}
f += 1ll * c[u] * deep;
csum[u] += c[u];
};
dfs(dfs, 0, 0, 0);
LL ans = f;
auto dfs2 = [&](auto self, int u, int fa) -> void {
ans = min(ans, f);
for (auto v : edge[u]) {
if (v == fa)
continue;
f -= csum[v];
f += sum - csum[v];
self(self, v, u);
f -= sum - csum[v];
f += csum[v];
}
};
dfs2(dfs2, 0, 0);
cout << ans << '\n';
return 0;
}
F - Oddly Similar (abc348 F)
題目大意
給定\(n\)個序列\(a_i\),長度為\(m\),問倆倆序列相似的對數。
倆序列相似,說明有奇數個位置,其數相同。
解題思路
考慮樸素做法,列舉兩個序列,然後列舉下標,記錄數相同的個數,其時間複雜度是\(O(n^2m)\)。判斷數相同如果不用 \(if\)的話,在 \(clang\)下能跑過(吸氧太猛了。
n3方過2e3
#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<int>> a(n, vector<int>(m, 0));
for (auto& x : a)
for (auto& y : x)
cin >> y;
int ans = 0;
for (int i = 0; i < n; i++) {
for (int j = i + 1; j < n; j++) {
int sum = 0;
for (int k = 0; k < m; k++) {
sum ^= (a[i][k] == a[j][k]);
}
ans += sum;
}
}
cout << ans << '\n';
return 0;
}
樸素做法是\(O(nnm)\),分別是列舉\(j\)(一行),列舉 \(i\)(另一行),再列舉 \(k\)(列)。考慮對其中一個因素最佳化。經過嘗試,我們可以對列舉\(i\)最佳化。
其實\(O(nnm)\)是非常接近 \(10^9\)的,一般是可以採用 \(bitset\)最佳化,總複雜度可以除以 \(64\)。
我們記\(cnt[k][l]\)表示第 \(k\)列值為 \(l\)的那些行的情況,即一個 \(bitset\), \(cnt[k][l][i]=1\)表示第\(i\)行 第\(k\)列的值為\(l\), \(=0\)則不為 \(l\)。
列舉一行 \(j\),然後列舉一列 \(k\),我們將所有的 \(cnt[k][a_{j,k}]\)(這些都是 \(bitset\))異或起來,最後得到的就是第 \(j\)行與其餘行的相似度情況(同下標數相等的個數的奇偶性),統計其為 \(1\)的個數即為 \((i,j)\)相似的 \(i\)的數量。注意要把 \(i=j\)的情況去掉。
時間複雜度是\(O(\frac{n^2m}{64})\)
神奇的程式碼
#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<int>> a(n, vector<int>(m));
vector<vector<bitset<2000>>> cnt(m, vector<bitset<2000>>(1000));
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
cin >> a[i][j];
--a[i][j];
cnt[j][a[i][j]][i] = 1;
}
}
int ans = 0;
for (int i = 0; i < n; i++) {
bitset<2000> s{};
for (int j = 0; j < m; j++) {
s ^= cnt[j][a[i][j]];
}
s[i] = 0;
ans += s.count();
}
ans /= 2;
cout << ans << '\n';
return 0;
}
G - Max (Sum - Max) (abc348 G)
題目大意
給定長度為\(n\)的陣列 \(a,b\),對 \(k = 1,2,...,n\),求解以下答案。
選擇\(k\)個下標,使得 \(\sum_{k} a_k - \max_{k} b_k\) 最大。
解題思路
<++>
神奇的程式碼