A - Raise Both Hands
題意:
給出Snuke舉的左右手情況,如果只舉左手,輸出Yes,如果只舉右手,輸出No,否則輸出Invalid
思路:
- 舉左手:
(l == 1 && r == 0)
- 舉右手:
(l == 1 && r == 0)
- 其他情況都是
Invalid
void solve()
{
int l = read(), r = read();
if(l == 1 && r == 0){
cout<<"Yes"<<endl;
}else if(l == 0 && r == 1){
cout<<"No"<<endl;
}else {
cout<<"Invalid"<<endl;
}
}
B - Binary Alchemy
題意:
有 \(N\) 種編號為 \(1, 2, \ldots, N\) 的元素。
元素之間可以相互組合。當元素 \(i\) 和 \(j\) 組合在一起時,如果 \(i \geq j\) 變為元素 \(A_{i, j}\) ,如果 \(i < j\) 變為元素 \(A_{j, i}\) 。
從元素 \(1\) 開始,依次與元素 \(1, 2, \ldots, N\) 結合。求最後得到的元素。
思路:
根據題意,模擬查表,逐一合成即可
void solve()
{
int n = read();
int a[N][N];
for(int i=1;i<=n;i++) for(int j=1;j<=i;j++) a[i][j] = read();
int x = 1;
for(int t=1;t<=n;t++){
int i = max(x,t) , j = min(x,t);
x = a[i][j];
}
cout<<x<<endl;
}
C - Word Ladder
題意:
給定兩個長度相等的字串 \(S\) 和 \(T\),
用最小的操作次數,使得 \(S = T\) ,並且字串 \(X\) 的字典序最小。
操作為,選擇 \(S_i = c\) ,並且將修改後的 \(S\) 放入 \(X\) 的末尾。
思路:
- 顯然S和T有多少個不同的字元就操作多少次就是次數最少
- 考慮使X的字典序最小,在S中修改字元,越靠前並且越小的字元應該越靠後修改
- 故兩次遍歷S,先從前往後遍歷,若\(S_i < T_i\)則操作一次
- 再從後往前遍歷,將剩下不一樣的字元修改掉
void solve()
{
string s1 = sread(), s2 = sread();
int n = s1.length();
vector<string> ans;
for(int i=0;i<n;i++){
if(s1[i] > s2[i]){
s1[i] = s2[i];
ans.push_back(s1);
}
}
for(int i=n-1;i>=0;i--){
if(s1[i]!=s2[i]){
s1[i] = s2[i];
ans.push_back(s1);
}
}
cout<<ans.size()<<endl;
for(auto it:ans) cout<<it<<endl;
}
D - Cross Explosion
題意:
\(H \times W\) 的二維網格,初始所有格子都有牆
\(Q\) 次操作,每次操作都在\((x , y)\)處放一炸彈
- 若\((x , y)\)沒有牆,則炸燬牆
- 若\((x , y)\)有牆,則分別炸燬上下左右的最近的第一面牆
思路:
對於每一個座標\((x , y)\),可以分成行和列來考慮
- 在第x行中,可以用二分查詢找到第一個座標大於等於y的牆
- 在第y列中,可以用二分查詢找到第一個座標大於等於x的牆
因此,我們需要一個 既能二分查詢,也能插入,刪除的容器
顯然只有 STL::set 能夠滿足
將行和列分別建立set陣列,每次操作在行和列都執行即可
void solve()
{
string s1 = sread(), s2 = sread();
int n = s1.length();
vector<string> ans;
for(int i=0;i<n;i++){
if(s1[i] > s2[i]){
s1[i] = s2[i];
ans.push_back(s1);
}
}
for(int i=n-1;i>=0;i--){
if(s1[i]!=s2[i]){
s1[i] = s2[i];
ans.push_back(s1);
}
}
cout<<ans.size()<<endl;
for(auto it:ans) cout<<it<<endl;
}
E - Avoid K Partition
題意:
給定一個陣列 \(A\),劃分成若干個子區間,使得沒有子區間的和為 \(K\)
求劃分方案數
思路:
涉及到子區間的和,顯然要用到字首和來最佳化,預處理一個字首和陣列 pre[N]
- 線性DP: (會TLE)
設 \(f_i\) 表示當前列舉到的連續子序列以 \(i\) 結尾的不同方案數- \(i\) 從1到n遍歷
- \(j\) 從0到i-1,pre[i] - pre[j] 即為最後一個子序列的和,若該值不等於K,則將該劃分方案加入到 f[i]中
void solve()
{
int n = read(), k = read();
const int mod = 998244353;
vector<int> v(1);
for(int i=1; i<=n; i++) v.push_back(read());
vector<int> f(n+1),pre(n+1);
for(int i=1;i<=n;i++){
pre[i] = v[i] + pre[i-1];
}
f[0] = 1;
for(int i=1;i<=n;i++){
for(int j=0;j<i;j++){
if(pre[j] != pre[i] - k){
f[i] = (f[i] + f[j])%mod;
}
}
}
cout<<f[n]<<endl;
}
- 考慮最佳化,pre[j] 只要不等於 pre[i] - k 即可,因此不需要遍歷每個子區間,只需要在所有方案的和中減去子區間和是k的方案即可
- 用map來儲存不同子區間和的方案的個數,即pre[i]的方案個數(方案個數就是符合條件的f[i]的值)
- 用sum來記錄所有子序列的劃分方案的和
void solve()
{
int n = read(), k = read();
vector<int> v(1);
for(int i=0;i<n;i++) v.push_back(read());
const int mod = 998244353;
vector<int> pre(n+1),f(n+1);
unordered_map<int,int> cnt;
for(int i=1;i<=n;i++) pre[i] = pre[i-1] + v[i];
f[0] = 1;
cnt[0] = 1;
int sum = 1;
for(int i=1;i<=n;i++){
f[i] = sum - cnt[pre[i] - k];
f[i]%=mod, f[i]+=mod, f[i]%=mod;
sum = (sum+f[i])%mod;
cnt[pre[i]] += f[i];
}
cout<<f[n]<<endl;
}
- 注意到:若沒有和為 \(K\) 的條件,長度為n的陣列劃分方案的個數為2的n次方