SMU Summer 2024 Contest Round 5
Robot Takahashi
思路
按照 Wi𝑊𝑖 排個序,算一下字首字尾 1
和 0
的個數就行了。答案大概是一個 \(\max(ans,pre_i+suf_{i+1})\) 的形式。
排序之後當 \(W_i=W_{i+1}\) 時無法在 i,i+1 之間斷開,要特判。
程式碼
#include<bits/stdc++.h>
using namespace std;
using i64 = long long;
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n;
cin >> n;
string s;
cin >> s;
s = " " + s;
vector<array<int, 2>> w(n + 1);
for (int i = 1; i <= n; i ++) {
cin >> w[i][0];
w[i][1] = s[i] - '0';
}
sort(w.begin() + 1, w.end());
vector<int> pre(n + 1), suf(n + 2);
for (int i = 1; i <= n; i ++)
pre[i] = pre[i - 1] + (!w[i][1]);
for (int i = n; i >= 1; i --)
suf[i] = suf[i + 1] + w[i][1];
int ans = 0;
for (int i = 0; i <= n; i ++) {
if (w[i][0] != w[i + 1][0]) {
ans = max(ans, pre[i] + suf[i + 1]);
}
}
cout << ans << '\n';
return 0;
}
Connect 6
題意
有一個 \(N\times N\) 的棋盤網格用 \(N\) 行字串 \(S_i\) 來表示。如果 \(S_{i,j}\) 是 #
,說明棋盤的第 \(i\) 行第 \(j\) 列有一個棋子,否則如果 \(S_{i,j}\) 是 .
,說明沒有棋子。
請你判斷是否可以再加入最多兩個棋子使得棋盤存在六子連。六子連的定義是,存在某行、某列或者某對角線上有連續的六個棋子。
思路
暴力判斷即可。
程式碼
#include<bits/stdc++.h>
using namespace std;
using i64 = long long;
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n;
cin >> n;
vector<string> s(n);
for (auto &i : s)
cin >> i;
int ans = 0;
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
if (i + 5 < n) { //列
int cnt = 0;
for (int k = i; k <= i + 5; k++)
cnt += s[k][j] == '.';
ans |= cnt <= 2;
}
if (j + 5 < n) { //行
int cnt = 0;
for (int k = j; k <= j + 5; k++)
cnt += s[i][k] == '.';
ans |= cnt <= 2;
}
if (i + 5 < n && j + 5 < n) { // 主對角線
int cnt = 0;
for (int k = 0; k <= 5; k++)
cnt += s[i + k][j + k] == '.';
ans |= cnt <= 2;
}
if (i - 5 >= 0 && j + 5 < n) { // 副對角線
int cnt = 0;
for (int k = 0; k <= 5; k++)
cnt += s[i - k][j + k] == '.';
ans |= cnt <= 2;
}
}
}
puts(ans ? "Yes" : "No");
return 0;
}
Strange Balls
題意
高橋君收到了 \(N\) 個奇怪的球,球擺成一列,每個球的表面都寫著一個數字,第 \(i\) 個球的表面數字是 \(a_i\)。
高橋君準備將所有的球從 \(1\) 到 \(N\) 依次放入桶中。桶是圓柱形的,底面是封死的,只能從圓柱形頂端放入。桶比較窄,桶中的球只能全部豎著疊放。
高橋君在放球的過程中,奇怪的事情發生了,如果桶中有連續 \(x\) 個值為 \(x\) 的球,這些球將會消失。
請你幫助高橋君計算出,從 \(1\) 到 \(N\) 依次放入每個球后,桶中的球有多少個?
思路
用棧記錄每次放入的元素,cnt 陣列記錄棧中棧頂值連續相同的個數,當棧頂值連續個數與值相同時,從棧中彈出相應個數即可。
程式碼
#include<bits/stdc++.h>
using namespace std;
using i64 = long long;
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n;
cin >> n;
vector<int> a(n + 1);
for (int i = 1; i <= n; i ++)
cin >> a[i];
vector<int> st, cnt(n + 1);
for (int i = 1; i <= n; i ++) {
int siz = st.size();
if (siz && a[i] == st.back())
cnt[siz] = cnt[siz - 1] + 1;
else
cnt[siz] = 1;
st.push_back(a[i]);
if (cnt[siz] == a[i]) {
int k = a[i];
while (k--)
st.pop_back();
}
cout << st.size() << '\n';
}
return 0;
}
Linear Probing
題意
維護一個長度為 \(2^{20}\) 的,下標從 \(0\) 到 \(2^{20}-1\) 的數列 \(a\)。初始時,數列中的每一項均為 \(-1\)。令 \(n=2^{20}\)。
給定 \(q\) 次操作,每次操作內容如下:
1 x
:將變數 \(h\) 的值定為 \(x\)。將 \(h\) 不斷加 \(1\) 直到 \(a_{h \bmod n} = -1\) 為止。令 \(a_{h \bmod n}\) 的值為 \(x\)。2 x
:輸出 \(a_{x \bmod n}\) 的值。
思路
考慮並查集思想。
首先所有的節點父親都是自己,一旦自己被修改了,那就讓自己的父親指向右邊第一個不為 -1 的節點,注意取模與開 longlong,還有路徑壓縮。
程式碼
#include<bits/stdc++.h>
using namespace std;
using i64 = long long;
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
const int N = 1 << 20;
vector<i64> fa(N), a(N, -1);
iota(fa.begin(), fa.end(), 0);
auto find = [&](auto & self, int x)->int{
return fa[x] == x ? x : fa[x] = self(self, fa[x]);
};
int q;
cin >> q;
while (q --) {
i64 op, x;
cin >> op >> x;
if (op == 1) {
int y = find(find, x % N);
a[y] = x;
fa[y] = find(find, (y + 1) % N);
} else {
cout << a[x % N] << '\n';
}
}
return 0;
}
Red Polyomino
題意
給你邊長為 𝑁N 的且僅由字元 #
和 .
組成的正方形陣列,其中 #
表示黑色格子, .
表示白色格子。你需要在白色格子中選擇 K 個塗成紅色,且使紅色格子互相連線(僅包括上下左右相鄰),求有多少種可能的方案。
思路
從 \(N\times N\) 的矩陣中選擇 \(K\) 個,最多有 \(C_{64}^8\) 種,考慮暴力。
暴力需要一點技巧,常規暴力是從紅色方塊向四周搜尋,但這樣會因為列舉的方向而使答案一直不對,需要反過來想透過 n 方遍歷地圖從空白格子向四周找紅色方塊。
程式碼
#include<bits/stdc++.h>
using namespace std;
using i64 = long long;
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n, k;
cin >> n >> k;
vector<string> s(n);
for (auto &i : s) cin >> i;
int ans = 0;
const int u[] = {1, -1, 0, 0};
const int v[] = {0, 0, 1, -1};
auto solve = [&](auto & self, int num) {
if (num == k) {
ans ++;
return ;
}
vector<pair<int, int>> loc;
for (int l = 0; l < n; l ++)
for (int c = 0; c < n; c++) {
for (int i = 0; i < 4; i ++) {
if (s[l][c] == '.') {
int dx = l + u[i];
int dy = c + v[i];
if (dx >= 0 && dx < n && dy >= 0 && dy < n && s[dx][dy] == 'r' ) {
s[l][c] = 'r';
self(self, num + 1);
s[l][c] = '#';
loc.push_back({l, c});
}
}
}
}
for (auto [x, y] : loc)
s[x][y] = '.';
};
for (int i = 0; i < n; i ++) {
for (int j = 0; j < n; j ++) {
if (s[i][j] == '.') {
s[i][j] = 'r';
solve(solve, 1);
s[i][j] = '#';
}
}
}
cout << ans << '\n';
return 0;
}
Stronger Takahashi
題意
有一個城鎮被劃分為H行和W列的單元格網格。
如果 \(S_i,_j\) 是' . ',則為道路;如果 \(S_i,_j\) 為' # ',則為障礙物。
高橋將從家裡去魚市。他的房子在左上角的方格,魚市在右下角的方格。
高橋可以從一個單元格向上、向下、向左或向右移動到可透過的單元格。他不能離開小鎮,也不能進入街區。
但是,他可以一次摧毀一個 2$\times$2 正方形區域中的所有障礙物,使這個區域可以透過,但需要消耗一點能量。
找到高橋進入魚市需要消耗的最少能量。
思路
不使用能量時,轉移消耗為 0 ,使用能量時,消耗 1 能量,則為典型的 01 bfs,使用雙端佇列跑 bfs 即可。
程式碼
#include<bits/stdc++.h>
using namespace std;
using i64 = long long;
int dx1[4][2] = {{0, -1}, { -1, 0}, {1, 0}, {0, 1}};
int dx2[25][2] = {{ -1, -1}, {1, 1}, { -1, 1}, {1, -1},
{2, -1}, {2, 1}, {1, 2}, {1, -2}, { -2, 1}, { -2, -1},
{ -1, -2}, { -1, 2}, {2, 0}, {0, 2}, { -2, 0}, {0, -2}
};
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n, m;
cin >> n >> m;
vector<string> s(n);
for (auto &i : s)
cin >> i;
vector dis(n, vector<i64>(m, INT_MAX));
deque<array<int, 2>> Q;
Q.push_front({0, 0});
dis[0][0] = 0;
while (Q.size()) {
auto [x, y] = Q.front();
Q.pop_front();
for (int i = 0; i < 4; i ++) {
int dx = x + dx1[i][0], dy = y + dx1[i][1];
if (dx >= 0 && dx < n && dy >= 0 && dy < m && s[dx][dy] == '.') {
if (dis[dx][dy] > dis[x][y]) {
dis[dx][dy] = dis[x][y];
Q.push_front({dx, dy});
}
}
}
for (int i = 0; i < 16; i ++) {
int dx = x + dx2[i][0], dy = y + dx2[i][1];
if (dx >= 0 && dx < n && dy >= 0 && dy < m) {
if (dis[dx][dy] > dis[x][y] + 1) {
dis[dx][dy] = dis[x][y] + 1;
Q.push_back({dx, dy});
}
}
}
}
cout << dis[n - 1][m - 1] << '\n';
return 0;
}
Predilection
題意
有一個長度為 N 的數列 A。
你可以進行若干次,最多 N−1 次操作,選擇相鄰的兩個數,刪去他們,並在原位置放上他們兩個的和。
現在你需要求出可能產生的序列個數。
思路
考慮 dp。
考慮對這個陣列進行字首和操作,用 \(num_i\) 表示第 𝑖 個數,則 \(𝑎_𝑖=∑_{𝑗=1}^𝑖𝑛𝑢𝑚_𝑗\)。我們發現合併第 𝑖 和 𝑖+1 個數合併第 𝑖 和 𝑖+1 個數後進行字首操作,實際上就是刪除了 \(𝑎_𝑖\)。對於合併第 𝑖 和 𝑖+1 個數的操作,𝑖 最多取到 𝑛−1,即 𝑖 < 𝑛,從中得出在刪除操作中,\(𝑎_𝑛\) 必須保留。因為每個字首和只能對應一個數,於是題意簡化成:對給出的 𝑛 個數進行字首和操作,每次可以刪除任意一個 \(𝑎_𝑖(𝑖<𝑛)\),求共有多少種不同的序列。可以發現,實際上就是求字首陣列 \(𝑎_1∼𝑎_{𝑛−1}\) 的不同子序列的個數,我們考慮 DP 求解。
-
若此數之前出現過,\(dp_𝑖=dp_{𝑖−1}\times 2−dp_{𝑙𝑎𝑠𝑡-1}\) 這裡是因為我們若接上上一個出現過的所有東西都會和上一個接上重複。
-
否則,\(dp_𝑖=dp_{𝑖−1}×2+1\) 這裡先接上前面的所有在加上自己。
最後輸出 \(dp_{n-1}+1\)。(加上空子序列)。
程式碼
#include<bits/stdc++.h>
using namespace std;
using i64 = long long;
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n;
cin >> n;
const int mod = 998244353;
vector<i64> a(n + 1);
for (int i = 1; i <= n; i ++) {
cin >> a[i];
a[i] += a[i - 1];
}
map<i64, int> mp;
vector<i64> dp(n + 1);
dp[1] = 1, mp[a[1]] = 1;
for (int i = 2; i <= n; i ++) {
int last = mp[a[i]];
if (!last) dp[i] = (dp[i - 1] * 2 + 1) % mod;
else dp[i] = (dp[i - 1] * 2 % mod - dp[last - 1] + mod) % mod;
mp[a[i]] = i;
}
cout << dp[n - 1] + 1 << '\n';
return 0;
}