AtCoder Beginner Contest 242(C~E)
AB 水題
C - 1111gal password
題意:給出 N(\(2\le N\le 1e6\))求滿足以下條件的 \(X\) 的數量,需除以模 (\(998244353\))
- $X $ 是 \(N\) 位數
- 對於 \(X_1,X_2,...,X_N\) 的每位數
- \(1\le X_i \le 9\)
- \(|X_i - X_{i+1}| \le 1\)
思路:
emm,直接看過去知道是比較明顯的數論推導問題,隨便寫了幾組發現當定義狀態 \(f_{i,j}\) 表示長度為 \(i\) 符合條件的數字,結尾數位是 \(j\) 有多少個。遞推式如下:
\[\begin{array}{c} f_{i, j}=f_{i-1, j-1}+f_{i-1, j}+f_{i-1, j+1} \\ f_{i, 0}=f_{i, 10}=0 \\ f_{1, j}=1 \end{array} \]【AC Code】
const int N = 1e6 + 10, mod = 998244353; ll n; ll f[N][11]; int main() { ios::sync_with_stdio(false), cin.tie(nullptr); cout << fixed << setprecision(20); cin >> n; for (int i = 1; i <= 9; i += 1) f[1][i] = 1; for (int j = 2; j <= n; j++) for (int i = 1; i <= 9; i++) f[j][i] = (f[j - 1][i - 1] + f[j - 1][i] + f[j - 1][i + 1]) % mod; // 定義初始和結尾以及初始值 // {1,2,3,4,5}, accumulate(a[0], a[5],0ll) = 15 cout << accumulate(f[n] + 1, f[n] + 10, 0ll) % mod; }
D - ABC Transform
題意:
給定一個長度為 \(N\) 的字串 \(S\) ,由A
B
C
三種字元組成,每一次變化會使 \(S\) 中的 A
全部變成 BC
,B
全部變成 CA
,C
全部變成 AB
,如 ABC
在一次變化後會變成 BCCAAB
。現在有 Q
個詢問,每個詢問都是求原串 \(S\) 經過 \(t_i\) 次變化後第 \(k_i\) 個字元
分析:
首先可以看出一個字元執行了 \(t\) 次操作以後的長度是好計算的,所以我們先把問題變成一個字元執行 \(t\) 次操作之後的第 \(k\) 個是什麼。
上面的東西可以考慮作為遞迴做,即定義函式類似 \(f(c,t,k)\)
表示字元 \(c\) 執行了 \(t\) 次操作得到的字串第 \(k\) 個是什麼,只是存在一個問題:\(t(\le 1e18)\) 太大了觀察到每次操作會把字串長度加倍,所以執行很少操作後,就變成了對 \(f(c,t,1)\) 求值。然而操作三次以後第一個字元等價於沒有改變,所以可以直接得到結果。
Update:發現原思路雖然正確但有點繞
令 $f(t,k) $ 為原 \(S\) 經過 \(t_i\) 次變化後得到的 \(k_i\) 字元
首先,當 \(t=0\) 時,直接輸出 \(S_k\) 即可
其次,當 \(t \not= 0\) ,我們需要知道 \(f(t,k)\) 從何而來
樣例
ABC ->
BCCAAB ->
CAABABBCBCCA
採用分類討論的方法,不難發現
- 當 $ k\in {2m+1 | m\in Z}$ (即 \(k\) 為奇數時),\(f(t - 1, \frac{k + 1}2) \to f(t,k)\)
- 當 $ k\in {2m | m\in Z}$ (即 \(k\) 為偶數時),\(f(t - 1, \frac{k}2) \to f(t,k)\)
怎麼進行變化?
可以概闊為一個 \(g\) 函式:
inline char g(char ch, int x) { return (ch - 'A' + x) % 3 + 'A';}
但是,如果我們每一次都去遞迴 \(t\) 還是太大了,直接炸了。那麼只能動 \(k\) 了
觀察樣例中字串首,它們是 A
B
C
輪換的。
利用這條性質,當 \(k=0\) 即可直接求 \(f(t,0) = g(S.front(),t mod 3)\)
但是問題來了,這樣一個演算法肯定會對 \(k\) 取模,萬一 \(k = 0...\)
所以,我們把 \(S\) 的改為編號 \(0 \to N-1\)
此時:
- 當 $ k\in {2m+1 | m\in Z}$ (即 \(k\) 為奇數時),\(f(t - 1, \frac{k - 1}2) \to f(t,k)\)
- 當 $ k\in {2m | m\in Z}$ (即 \(k\) 為偶數時),\(f(t - 1, \frac{k}2) \to f(t,k)\)
程式碼就很好寫了
string s;
ll q, t, k;
inline char g(char ch, int x) { return (ch - 'A' + x) % 3 + 'A';}
char f(ll t, ll k) {
if (!t) return s[k];
if (!k) return g(s[0], t % 3);
return g(f(t - 1, k >> 1), (k & 1) + 1);
}
int main() {
ios::sync_with_stdio(false), cin.tie(nullptr);
cout << fixed << setprecision(20);
cin >> s >> q;
while (q --) {
cin >> t >> k;
cout << f(t, k - 1) << "\n";
}
}
E - (∀x∀)
title 好玩
我們可以列舉兩個字串相同的字首有多長,然後在後一個位置給串 \(X\) 填更小的字元,再之後的位置可以隨便填了。
- 需要判斷 \(X\) 的前半段全部和 \(S\) 相同時,後半段是否小於等於 \(S\)。
const int N = 1e6, mod = 998244353;
ll pw[N + 10] = {1};
string s;
int T, n;
int main() {
ios::sync_with_stdio(false), cin.tie(nullptr);
cout << fixed << setprecision(20);
for (int i = 1; i <= N; i += 1) pw[i] = pw[i - 1] * 26ll % mod;
cin >> T;
while (T--) {
cin >> n >> s;
s = "@" + s;
int ans = 0;
bool f = 1;
for (int i = 1; i <= (n + 1) / 2; i++) {
int a = s[i] - 'A', b = s[n + 1 - i] - 'A';
ans = (ans + (ll)a * pw[(n + 1) / 2 - i]) % mod;
f &= a <= b;
f |= a < b;
}
cout << (ans + f) % mod << "\n";
}
}
Tips:時間會解決一切