A - Exponential Plant (abc354 A)
題目大意
某星球上的植物,初始高\(0\),然後每天依次增長 \(1,2,4,8,...\),問哪天就高過身高為\(h\)的高橋。
解題思路
因為是指數級別長高,列舉一下天數即可,由於\(h \leq 10^9\),因此天數不會超過 \(32\)天。
神奇的程式碼
#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 x;
cin >> x;
++x;
int cnt = 0;
while (x) {
cnt++;
x >>= 1;
}
cout << cnt << '\n';
return 0;
}
B - AtCoder Janken 2 (abc354 B)
題目大意
給定\(n\)個人的名字和分數。
按名字字典序給他們排序。
然後設他們總分數為 \(sum\),則第 \(sum % 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;
int sum = 0;
vector<pair<string, int>> a(n);
for (auto& i : a) {
cin >> i.first >> i.second;
sum += i.second;
}
vector<int> id(n);
iota(id.begin(), id.end(), 0);
sort(id.begin(), id.end(),
[&](int i, int j) { return a[i].first < a[j].first; });
int win = id[sum % n];
cout << a[win].first << '\n';
return 0;
}
C - AtCoder Magics (abc354 C)
題目大意
\(n\)個卡牌,有對應的強度\(a_i\)和花費 \(c_i\)。
對於兩個卡牌 \(i,j\),如果 \(a_i > a_j\)且 \(c_i < c_j\),則卡牌 \(j\)會被丟棄。
不斷進行如上操作,問最終的牌是哪些。
解題思路
對每個卡牌的代價\(c\)從小到大排序,然後依次考慮當前卡牌\(j\)是否要丟棄。
因為是按順序列舉 \(j\),則 \(i < j\)的卡牌都滿足 \(c_i < c_j\),如果存在\(a_i > a_j\),則說明當前卡牌要丟棄。
因為是存在,所以我們維護 \(\max_{1 \leq i < j}(a_i)\),一個字首的強度最大值,如果 \(max_a > a_j\),說明當前卡牌 \(j\)要丟棄,否則就不用丟棄。
把不需要丟棄的卡牌放到一個陣列裡,然後輸出即可。
神奇的程式碼
#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>> a(n);
for (auto& x : a)
cin >> x[0] >> x[1];
vector<int> id(n);
iota(id.begin(), id.end(), 0);
sort(id.begin(), id.end(), [&](int i, int j) { return a[i][1] < a[j][1]; });
vector<int> ans;
int maxx = 0;
for (auto x : id) {
if (a[x][0] < maxx)
continue;
ans.push_back(x);
maxx = max(maxx, a[x][0]);
}
cout << ans.size() << '\n';
sort(ans.begin(), ans.end());
for (auto x : ans) {
cout << x + 1 << " ";
}
cout << '\n';
return 0;
}
D - AtCoder Wallpaper (abc354 D)
題目大意
給定一個如下定義的二維網格。
給定一個矩形區域,問該區域的黑色部分面積的兩倍是多少。
解題思路
非常好的圖,讓我不知所措
定眼一看,發現只有本質不同的兩類行(注意圖裡的水平線僅在\(y\)是偶數的行),每行的形狀具有迴圈節,迴圈節長度為\(4\)。
因此我們只需考慮考慮兩行,最多長度為\(3\)的區域,其餘的面積可以直接透過迴圈節算出來。
一個是偶數行(\(y = 0 \to 1\)),從 \((0,0)\)來往\(x\)正方向看, 迴圈節為\(4\)的黑色面積的兩倍分別為 \(2, 1, 0, 1\)。奇數行則為\(1, 2, 1, 0\)。
因此矩形區域\((a,b) -> (c,d)\) ,考慮\(a \to c\)的偶數行,可以計算出其黑色面積的大小,一個是迴圈節的大小 \(\lfloor \frac{c - a}{4} \rfloor \times (2 + 1 + 1)\),然後是多餘的一小部份長度\((c - a) \% 4\),這個直接暴力計算即可。然後再\(\times\)偶數行的數量。
同理計算出奇數行的面積大小\(\times\)奇數行的數量,兩者的和即為答案。
神奇的程式碼
#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 a, b, c, d;
cin >> a >> b >> c >> d;
array<array<int, 4>, 2> area{{{2, 1, 0, 1}, {1, 2, 1, 0}}};
int sum = 4;
auto solve = [&](int odd, int l, int r, int h) {
LL ss = 1ll * (r - l) / 4 * sum * h;
int cnt = (r - l) % 4;
while (cnt--) {
int left = ((l % 4) + 4) % 4;
ss += 1ll * area[odd][left] * h;
l++;
}
return ss;
};
int odd = (abs(b) % 2);
LL one = solve(odd, a, c, (d - b + 1) / 2);
LL two = solve(odd ^ 1, a, c, (d - b) / 2);
cout << one + two << '\n';
return 0;
}
E - Remove Pairs (abc354 E)
題目大意
給定\(n\)張卡牌,每張卡牌正反各寫一個數字,高橋和青木玩遊戲,每回合,一個人可拿走兩張正面數字一樣或反面數字一樣的卡牌。不能操作者人輸。
高橋先,問兩者絕頂聰明的情況下,誰贏。
解題思路
樸素的博弈\(dp\)。由於 \(n \leq 18\),可以直接設 \(dp[i]\)表示現在還有的卡牌情況(一個二進位制壓縮狀態)是\(i\)的情況下,先手是必贏還是必輸。
要看其是必輸還是必贏,則需要看後繼狀態是否存在必輸態
,如果存在必輸態,則當前狀態\(i\)可以透過對應的轉移變成先手必輸態 \(j\)(從當前態 \(i\)來看就是後手必輸了),則說明當前狀態 \(i\)是必贏,即 \(dp[i] = 1\),否則如果所有後繼狀態都是必贏態,則說明當前是必輸態,即 \(dp[i] = 0\)。
而後繼狀態就是當前狀態可以做的決策的轉移,即選擇兩張正面數字一樣或反面數字一樣的卡牌拿走。花\(O(n^2)\)列舉下選擇的兩張牌即可。
總的時間複雜度是 \(O(n^22^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<array<int, 2>> a(n);
for (auto& x : a)
cin >> x[0] >> x[1];
int up = (1 << n);
vector<int> dp(up, -1);
dp[0] = 0;
auto dfs = [&](auto dfs, int s) -> int {
if (dp[s] != -1)
return dp[s];
int ok = 0;
for (int i = 0; i < n; i++) {
if ((~s >> i) & 1)
continue;
for (int j = i + 1; j < n; j++) {
if ((~s >> j) & 1)
continue;
if (a[i][0] != a[j][0] && a[i][1] != a[j][1])
continue;
ok |= !dfs(dfs, s ^ (1 << i) ^ (1 << j));
}
}
return dp[s] = ok;
};
int ok = dfs(dfs, up - 1);
if (ok) {
cout << "Takahashi" << '\n';
} else {
cout << "Aoki" << '\n';
}
return 0;
}
F - Useless for LIS (abc354 F)
題目大意
給定一個陣列\(a\)。
對於每個數字 \(a_i\),問在\(a\)的最長上升子序列中,\(a_i\)是否存在其中。
多組詢問。
解題思路
首先肯定要求一遍最長上升子序列的長度,由於\(n \leq 2e5\),得用\(O(n \log n)\)的求法,假設我們求得的長度是\(LIS\)。
考慮如何判斷 \(a_i\)是否存在最長上升子序列中。
考慮最樸素的求上升子序列的求法,即 \(dp[i]\)表示前 \(i\)個數字,我選擇第\(i\)個數字的最長上升子序列的長度,轉移則列舉上一個數字是哪個。
如果\(a_i\)可以成為最長上升子序列中,那說明什麼?
我 \(1 \to i\)中,選擇 \(a_i\),得到最長上升序列長度 \(dp[i]\), 如果它可以成為最長上升子序列,則說明\(i \to n\)的最長上升子序列長度是 \(LIS - dp[i] + 1\)。這相當於從右往左考慮的最長下降子序列 \(dp_2[i] = LIS - dp[i] + 1\)。
因此我們只需要求出從左到有的最長上升子序列長度\(dp[i]\)和從右往左的最長下降子序列長度 \(dp_2[i]\)。然後對於每個 \(a_i\),如果 滿足 \(dp[i] + dp_2[i] - 1 == LIS\),則說明 \(a_i\)會存在 \(a\)的最長上升子序列中。
那現在的問題就是如何求\(dp\)和 \(dp2\),樸素的 \(dp\)的求法是 \(O(n^2)\),對於這裡的 \(n \leq 2e5\)會超時。
考慮 \(O(n \log n)\)的求法,事實上可以從這還原出\(dp\)。
即 \(len[i]\)表示最長上升子序列長度為 \(i\)的末尾數字(最大數字)的最小值。(和上面的區別相當於把 \(dp\)的值作為狀態,條件變成了值),注意到 \(len\)是一個遞增的陣列,因此對於當前數字\(a_i\) ,可以二分找到它可以接在哪個數字\(a_j\)的後面,進而知道了當前的\(dp[i]\),然後更新 \(len\)陣列。
程式碼中求 \(dp2\)是將 \(a\)左右翻轉然後全部取相反數,就相當於再求一個從左到右的最長上升子序列了。
神奇的程式碼
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
const int inf = 1e9 + 7;
int main(void) {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int t;
cin >> t;
while (t--) {
int n;
cin >> n;
vector<int> a(n);
for (auto& x : a)
cin >> x;
int LIS = 0;
auto solve = [&](vector<int>& a) -> vector<int> {
vector<int> dp(n + 1, inf), len(n, 1);
dp[0] = -inf;
for (int i = 0; i < n; ++i) {
int pos = lower_bound(dp.begin(), dp.end(), a[i]) - dp.begin();
len[i] = pos;
dp[pos] = min(dp[pos], a[i]);
LIS = max(LIS, pos);
}
return len;
};
auto l = solve(a);
reverse(a.begin(), a.end());
for (auto& i : a)
i *= -1;
auto r = solve(a);
reverse(r.begin(), r.end());
vector<int> ans;
for (int i = 0; i < n; ++i) {
if (l[i] + r[i] - 1 == LIS)
ans.push_back(i + 1);
}
cout << ans.size() << '\n';
for (auto x : ans)
cout << x << ' ';
cout << '\n';
}
return 0;
}
G - Select Strings (abc354 G)
題目大意
給定\(n\)個字串。字串有價值。
選定一些字串,使得字串倆倆之間不存在子串的關係,最大化價值。
解題思路
首先可以花\(O(\max(n^2, (\sum |s|)^2))\) 求出倆倆字串之間的不可選擇關係。
剩下的問題就是有一個圖,點有點權,點之間的連邊表示不能同時選。然後選些點,點權值最大。
感覺很像一個最小割
神奇的程式碼