A - Leftrightarrow
Question
給你一個由 <
、=
和 >
組成的字串 \(S\) 。
請判斷 \(S\) 是否是雙向箭頭字串。
字串 \(S\) 是雙向箭頭字串,當且僅當存在一個正整數 \(k\) ,使得 \(S\) 是一個 <
、 \(k\) 個 =
和一個 >
的連線,且順序如此,長度為 \((k+2)\) 。
Solution
按照題意模擬
Code
#include <bits/stdc++.h>
using namespace std;
int main() {
string s;
cin >> s;
if (s[0] != '<') {printf("No\n"); return 0;}
if (s.back() != '>') {printf("No\n"); return 0;}
for (int i = 1; i < s.size() - 1; i++) {
if (s[i] != '=') {printf("No\n"); return 0;}
}
printf("Yes\n");
}
B - Integer Division Returns
Quesiton
給定一個介於 \(-10^{18}\) 和 \(10^{18}\) 之間的整數 \(X\) ,列印 \(\left\lceil \dfrac{X}{10} \right\rceil\) 。
這裡, \(\left\lceil a \right\rceil\) 表示不小於 \(a\) 的最小整數。
Solution
分類討論,如果 \(a\) 能整除 \(10\),那麼答案就是 \(\frac{a}{10}\)
-
如果 \(a\) 是正數,輸出 \(\lfloor \frac{a}{10}\rfloor + 1\)
-
如果 \(a\) 是負數,輸出 \(\lfloor \frac{a}{10}\rfloor\)
Code
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
int main() {
ll n; cin >> n;
if (n % 10 == 0) {
cout << n / 10 << '\n';
}
else {
if (n > 0) {
cout << n / 10 + 1 << '\n';
}
else {
cout << n / 10 << '\n';
}
}
}
C - One Time Swap
Question
給你一個字串 \(S\) 。請找出以下操作 \(1\) 次的字串數。
- 設 \(N\) 是 \(S\) 的長度。選擇一對整數 \((i,j)\) 使得 \(1\leq i\lt j\leq N\) 和 \(S\) 的 \(i\) -th 和 \(j\) -th 字元互換。
Solution
考慮交換的對數是 \(\frac{n(n+1)}{2}\)
如果一個字母,和之前相同的字母交換,得到的還是原串,否則得到的串是一個新串
所以我們只需要統計每個字元與之前的多少個字母不同就可以得到答案
注意要判斷是否能得到原串
Code
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
int main() {
string s; cin >> s;
ll n = s.size(); s = " " + s;
vector<ll> num(26,0);
ll ans = 0, flg = 0;
for (int i = 1; i <= n; i++) {
if (num[s[i] - 'a'] > 0) flg = 1;
num[s[i] - 'a']++;
ans += i - num[s[i] - 'a'];
}
cout << ans + flg << '\n';
}
D - Tiling
Quesiton
有一個由 \(H\) 行和 \(W\) 列組成的網格,每個單元格的邊長為 \(1\) ,我們有 \(N\) 塊瓷磚。
其中的 \(i\) 瓷磚( \(1\leq i\leq N\) )是一個大小為 \(A_i\times B_i\) 的矩形。
請判斷是否有可能將這些瓷磚放置在網格上,從而滿足以下所有條件:
- 每個單元格都正好被一個圖塊覆蓋。
- 有未使用的瓦片也沒關係。
- 瓷磚在放置時可以旋轉或翻轉。但是,每塊瓦片必須與單元格的邊緣對齊,不得超出網格。
\(N\le7,H,W\le 10\)
Solution
考慮到 \(N,H,W\) 都特別小,直接暴力
直接列舉放瓷磚的順序以及每塊磚是否旋轉,最後暴力放磚即可
我在放磚的時候使用了優先佇列,所以時間複雜度為 \(O(HW\log HW)\)
實際上判斷放磚可以最佳化到 \(O(HW)\)
總時間複雜度就為 \(O(N!\cdot 2^N\cdot HW)\)
Code
#include <bits/stdc++.h>
using namespace std;
typedef vector<vector<int> > vvi;
typedef pair<int, int> pii;
int main() {
int n; cin >> n;
int H, W; cin >> H >> W;
vector<pii> a(n + 1);
for (int i = 1; i <= n; i++) {
cin >> a[i].first >> a[i].second;
}
auto check = [&] (vector<int> &id, int S) {
vector<vector<int> > v(H + 1, vector<int>(W + 1,0));
priority_queue<pii, vector<pii>, greater<pii> > pq;
for (int i = 1; i <= H; i++) {
for (int j = 1; j <= W; j++) {
pq.push({i,j});
}
}
for (int i = 1; i <= n; i++) {
auto &[dx, dy] = a[id[i]];
if (S >> (i - 1) & 1)
swap(dx, dy);
while (!pq.empty() && v[pq.top().first][pq.top().second]) pq.pop();
if (pq.empty()) {return 1;}
auto [x, y] = pq.top(); pq.pop();
for (int i_ = x; i_ < x + dx; i_++) {
for (int j_ = y; j_ < y + dy; j_++) {
if (i_ > H || j_ > W || v[i_][j_] == 1) return 0;
v[i_][j_] = 1;
}
}
}
while (!pq.empty() && v[pq.top().first][pq.top().second]) pq.pop();
if (pq.empty()) return 1;
return 0;
};
vector<int> id (n + 1);
iota(id.begin(), id.end(), 0);
do {
for (int S = 0; S < (1<<n); S++)
if (check(id, S)) {cout << "Yes\n"; return 0;}
} while (next_permutation(id.begin() + 1, id.end()));
cout << "No\n";
return 0;
}
E - Colorful Subsequence
Question
有 \(N\) 個球排成一排。
左邊的 \(i\) 個球的顏色是 \(C_i\) ,數值是 \(V_i\) 。
高橋希望在不改變順序的情況下,將這一行中的 \(K\) 個球移除,這樣在排列剩餘的球時,就不會有相鄰的兩個球顏色相同。此外,在此條件下,他希望最大化這一行中剩餘球的總價值。
請計算高橋是否能移除 \(K\) 個球,使剩下的一行中沒有相鄰的兩個球顏色相同。如果可以,求剩餘球的最大總值。
- \(1\leq K\lt N\leq 2\times 10^5\)
- \(K\leq 500\)
- \(1\leq C_i\leq N\)
- \(1\leq V_i\leq 10^9\)
Solution
先考慮一種樸素的 DP,定義 \(dp[i][j][col]\) 表示前 \(i\) 個球,以及移走了 \(j\) 個,末尾的最後一個球的顏色為 \(col\) 的最大值
考慮兩種情況:
- 移走第 \(i\) 個球,那麼轉移方程為 \(dp[i][j][col']=\max\{dp[i-1][j-1][col']\}\),其中 \(col'\) 為上一次球的顏色
- 不移走第 \(i\) 個球,那麼轉移方程為 \(dp[i][j][col]=\max\{dp[i-1][j][col']+v[i]\},c[i]\ne col'\)
這樣的時間複雜度為 \(O(N^2K)\) 會超時
觀察發現 \(dp[i][j-1][col]\) 的很多值都是空的,所以考慮值維護其最大值以及和最大值顏色不同的次大值,這樣子每次轉移肯定能從最大值和次大值裡面挑一個轉移,後面的小值就不需要去考慮了
所以我們修改一下定義
\(dp[i][j][p]\) 記錄前 \(i\) 個球,移走了 \(j\) 個,的最大值,次大值的 \(v[i]\) 總值和顏色
轉移過程和樸素情況一樣,只是當最大值和當前的 \(c[i]\) 相同時,改用次大值轉移
這樣的空間複雜度為 \(O(NK)\) 會超記憶體
發現 \(dp[i]\) 的狀態只與 \(dp[i-1]\) 有關,所以考慮使用滾動陣列把空間最佳化到了 \(O(K)\)
Code
#pragma GCC optimize(3)
#pragma GCC optimize("Ofast")
#pragma GCC optimize("unroll-loops")
#pragma GCC target("avx,avx2,fma")
#pragma GCC optimize(2)
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<ll,ll> pll;
const ll inf = 1e15;
int main() {
freopen ("E.in", "r", stdin);
int k ,n; cin >> n >> k;
vector<int> c(n + 1), v(n + 1);
for (int i = 1; i <= n; i++)
cin >> c[i] >> v[i];
const vector<pll> infv = {{-inf, -1}, {-inf, -2}};
vector<vector<vector<pll>>> dp(2); // 三維陣列 dp[i][j][col] 表示前 i 個物品中選了 j 個,且最後一個物品的顏色是 col 的最大價值
dp[0].resize(k + 1, infv); dp[1].resize(k + 1, infv);
dp[0][0][0].first = dp[0][0][1].first = 0;
for (int i = 1; i <= n; i++) {
const int col = c[i], val = v[i];
auto &cur = dp[i & 1], &pre = dp[(i - 1) & 1];
for (int j = 0; j <= k; j++)
cur[j] = infv;
//選了當前物品
for (int j = 0; j <= k; j++) {
for (auto &x : pre[j]) {
const ll preval = x.first, precol = x.second;
if (precol == col) continue;
cur[j].push_back({preval + val, col});
break;
}
}
//沒選當前物品
for (int j = 1; j <= k; j++) {
for (auto &x : pre[j - 1]) {
const ll preval = x.first, precol = x.second;
cur[j].push_back({preval, precol});
}
}
//去重
for (int j = 0; j <= k; j++) {
auto &a = cur[j];
sort(a.begin(), a.end()); reverse(a.begin(), a.end());
if (a[0].second == a[1].second) {
ll secval = -inf, seccol = -2;
for (auto x : a) {
if (x.second != a[0].second) {
secval = x.first;
seccol = x.second;
break;
}
}
a[1] = {secval, seccol};
}
a.resize(2);
}
}
ll ans = dp[n & 1][k][0].first;
cout << (ans < 0 ? -1 : ans) << endl;
return 0;
}
F - Many Lamps
Question
有一個簡單的圖,圖中有 \(N\) 個頂點,編號為 \(1\) 至 \(N\) ,有 \(M\) 條邊,編號為 \(1\) 至 \(M\) 。邊 \(i\) 連線頂點 \(u_i\) 和 \(v_i\) 。
每個頂點上都有一盞燈。初始時,所有的燈都是熄滅的。
請在 \(0\) 和 \(M\) 之間(包括首尾兩次)執行以下操作,以確定是否可以恰好開啟 \(K\) 盞燈。
- 選擇一條邊。假設 \(u\) 和 \(v\) 是邊的端點。切換 \(u\) 和 \(v\) 上的燈的狀態。也就是說,如果燈亮著,則將其關閉,反之亦然。
如果可以恰好開啟 \(K\) 盞燈,請列印實現該狀態的操作序列。
Solution
簡化問題,整個圖是連通的
有一個性質:開的燈的數量肯定是偶數,證明如下
每次對一條邊進行操作:
- 初始兩個燈都是亮的,那麼總亮燈數 \(-2\)
- 初始一亮一暗,那麼總亮燈數不變
- 初始兩個燈都是滅的,那麼總亮燈數 \(+2\)
奇偶性不變,初始是 \(0\) 為偶數,所以亮的燈的數量總是偶數
考慮連通圖的總數為 \(N\) ,設 \(X\) 為小於等於 \(N\) 的最大偶數,有一種構造方法能讓 \(X\) 盞燈都點亮
- 先建立連通圖的生成樹 \(G\)
- 取 \(G\) 的一個葉節點 \(u\),考慮與葉節點連線的其父親節點 \(v\)
- 如果 \(u\) 是滅燈狀態的話,那麼對 \(u-v\) 進行一次轉換操作,如果 \(u\) 是亮燈的話則不操作
- 刪除 \(u-v\) 這條邊
為什麼這樣是有效的,因為如果 \(u\) 是滅燈
- \(v\) 也是滅燈,那麼同時點亮了兩盞燈,能使亮燈數增加
- \(v\) 是亮燈,雖然亮燈數沒有變大,但是能把 \(u-v\) 的亮燈狀態進行調換,由於葉節點只連線父節點一盞燈,但父節點能連線很多其他燈,把亮的燈放到葉子節點,讓父節點更有機會和別的節點進行亮兩盞燈的操作
如果 \(K<X\) ,亮燈的過程是從 \(0\sim X\) 的連續偶數,當亮燈數 \(=X\) 時,中止過程即可
Code
#include <bits/stdc++.h>
using namespace std;
typedef pair<int, int> pii;
int main() {
freopen ("F.in", "r", stdin);
int n, m, k; cin >> n >> m >> k;
vector<vector<pii> > g(n + 1);
for (int i = 1; i <= m; i++) {
int u,v; cin >> u >> v;
g[u].push_back({v, i});
g[v].push_back({u, i});
}
int Y = 0;
vector<int> ans, vis(n + 1, 0), cur(n + 1, 0);
function<void(int)> dfs = [&](int u) {
vis[u] = 1;
for (auto [v, id] : g[u]) {
if (vis[v]) continue;
dfs (v);
if (cur[v] == 0 && Y < k) {
Y -= cur[u] + cur[v];
cur[u] ^= 1; cur[v] ^= 1;
Y += cur[u] + cur[v];
ans.push_back(id);
}
}
};
for (int i = 1; i <= n; i++)
if (!vis[i]) dfs(i);
if (Y != k) cout << "No" << endl;
else {
cout << "Yes" << endl;
cout << ans.size() << endl;
for (auto x : ans) cout << x << " ";
cout << endl;
}
return 0;
}
G - Sugoroku 5
Question
有一個棋盤遊戲,其中有 \(N+1\) 個方格:方格 \(0\) 、方格 \(1\) 、方格 \(\dots\) 和方格 \(N\) 。
你有一個骰子,它能擲出一個介於 \(1\) 和 \(K\) 之間的整數,每種結果的機率相等。
您從 \(0\) 開始擲骰子。重複下面的操作,直到到達 \(N\) 格:
- 擲骰子。假設 \(x\) 是當前方格, \(y\) 是擲出的數字,然後移動到方格 \(\min(N, x + y)\) 。
假設 \(P_i\) 是經過恰好 \(i\) 次操作後到達 \(N\) 個方格的機率。計算 \(P_1, P_2, \dots, P_N\) % \(998244353\)
\(1 \leq K \leq N \leq 2 \times 10^5\)
Solution
不會