題目傳送門:AtCoder Grand Contest 019。
A - Ice Tea Store
如果買兩瓶 250mL 的冰紅茶比買一瓶 500mL 的冰紅茶更便宜,就更新它。
同理,用 500mL 的更新 1L 的,用 1L 的更新 2L 的。
最終答案是 \(\lfloor N / 2 \rfloor \times S + (N \bmod 2) \times D\)。
#include <cstdio>
typedef long long LL;
int main() {
int Q, H, S, D; LL N;
scanf("%d%d%d%d%lld", &Q, &H, &S, &D, &N);
if (H > 2 * Q) H = 2 * Q;
if (S > 2 * H) S = 2 * H;
if (D > 2 * S) D = 2 * S;
printf("%lld\n", N / 2 * D + N % 2 * S);
return 0;
}
B - Reverse and Compare
如果選取的要 reverse 的區間 \([l, r]\),它兩端點的 \(A_l = A_r\),則產生的結果就會與區間 \([l + 1, r - 1]\) 相同了。
而每個 \(A_l \ne A_r\) 的區間 reverse 后,顯然是兩兩不同的(考慮最左側與原串不同的位置,即為 \(l\),同理確定 \(r\))。
那么就是統計 \(A_l \ne A_r\) 的數對個數,對每個字母開個桶統計即可。
#include <cstdio>
typedef long long LL;
const int MN = 200005, Sig = 26;
char s[MN];
int buk[Sig];
LL Ans;
int main() {
scanf("%s", s + 1);
for (int i = 1; s[i]; ++i)
Ans += i - ++buk[s[i] - 'a'];
printf("%lld\n", Ans + 1);
return 0;
}
C - Fountain Walk
顯然,因為 \(100\) 米太長了,不應該走出起點和終點確定的矩形內。
那么我們考慮起點到終點的路徑,我們要盡量最大化經過的 \(1 / 4\) 圓的數量,但與此同時要減少 \(1 / 2\) 圓的數量。
我們考慮計算出最多能經過的圓的數量,這可以通過求 LIS(最長上升子序列)得到。
假設會經過 \(k\) 個圓,經過的這 \(k\) 個圓都可以是 \(1 / 4\) 圓嗎?
不一定,如果 \(k = \min(|x_2 - x_1|, |y_2 - y_1|) + 1\) 的話,是必須有一個圓要變成 \(1 / 2\) 圓的。
否則可以證明一定可以讓這 \(k\) 個圓都成為 \(1 / 4\) 圓。其實還挺坑的,我本來以為只有起點和終點在同一直線上時才會有 \(1 / 2\) 圓。
#include <cstdio>
#include <cmath>
#include <algorithm>
typedef double r64;
const r64 PI = acos(-1);
const r64 C1 = 5 * (PI - 4);
const r64 C2 = 10 * (PI - 2);
const int Inf = 0x3f3f3f3f;
const int MN = 200005;
int sx, sy, tx, ty, N, K, px[MN], py[MN], p[MN];
r64 Ans;
int f[MN];
int main() {
scanf("%d%d%d%d%d", &sx, &sy, &tx, &ty, &N);
Ans = 100ll * (abs(tx - sx) + abs(ty - sy));
if (sx > tx) std::swap(sx, tx), std::swap(sy, ty);
int rev = sy > ty;
if (rev) std::swap(sy, ty);
for (int i = 1, x, y; i <= N; ++i) {
scanf("%d%d", &x, &y);
if (x < sx || tx < x || y < sy || ty < y) continue;
if (rev) y = -y;
px[++K] = x, py[K] = y, p[K] = K;
}
std::sort(p + 1, p + K + 1, [](int i, int j) { return px[i] < px[j]; });
int num = 0;
f[0] = -Inf;
for (int i = 1; i <= K; ++i) f[i] = Inf;
for (int i = 1; i <= K; ++i) {
int v = py[p[i]];
int j = std::lower_bound(f, f + K + 1, v) - f;
if (num < j) num = j;
f[j] = v;
}
if (num == std::min(tx - sx, ty - sy) + 1) Ans += (num - 1) * C1 + C2;
else Ans += num * C1;
printf("%.15lf\n", Ans);
return 0;
}
D - Shift and Flip
令 \(N = |A| = |B|\)。
注意到 \(N \le 2000\),我們只需要搞出一個 \(\tilde{\mathcal O} (N^2)\) 的做法就行。
如果 \(B\) 是全 \(0\) 串,特判一下即可,接下來假設 \(B\) 至少有一個 \(1\)。
考慮我們會把 \(A\) 轉來轉去,假設往右循環移位記作 \(+1\),往左記作 \(-1\)。
我們可以枚舉最終 \(A\) 到達的位置(就是說,相對初始位置轉了多少個單位,用 \(\pm 1\) 算),假設為 \(r\)。
注意,因為始終可以構造 \(2 N - 1\) 步內變成 \(B\) 的操作方法(轉一圈兒,再對每個位置都取反一次,因為卡不滿所以減 \(1\)):
我們只需在 \([1 - 2 N, 2 N - 1]\) 內枚舉 \(r\) 即可。
枚舉了 \(r\) 后,我們就得到了 \(A\) 中的哪些位置與 \(B\) 不同,是需要取反的,對這些位置我們可以預處理出兩個值:
如果往向右循環移位,需要移多少位才能到達一個 \(B\) 中的 \(1\),從而進行取反操作,同理可以處理出只往左循環移位的情況。
如果向右、向左中至少有一個已經落在了我們從 \(0\) 到達 \(r\) 的區間內了,就不需要花費額外的移位代價,只要多操作一次就行。
否則不僅要操作一次,還要產生額外的移位代價,如果需要向右的長度比右端點還往右,處理出多了多長。
如果選取了該位置要往右循環移位然后取反,移位時就至少要超出原右端點,超出這個長度這么多。
向左的長度同理,可以處理出來會多多少。
也就是對每個這樣的位置,有兩個值:向左會多多少,向右會多多少。
每個位置要選擇它是向左還是向右,而最終向左的長度,就是選取向左的位置中,向左的長度的最大值。向右同理。
而額外的移動代價,就是最終向左向右的長度之和,再乘以 \(2\)。
總之我們需要最小化向左向右的長度之和。這個的話,對於某一維排個序,然后做前綴后綴最大值就行了。
排序用基數排序的話,時間復雜度就是 \(\mathcal O (N^2)\) 的,代碼中直接 sort
了,時間復雜度會多一個 \(\log N\)。
#include <cstdio>
#include <cstring>
#include <algorithm>
const int Inf = 0x3f3f3f3f;
const int MN = 2005;
int N;
char s[MN], t[MN];
int A[MN], B[MN], L[MN], R[MN], dl[MN], dr[MN], p[MN], suf[MN];
inline int Solve(int r) {
int ret = 0, lb = 0, rb = 0, K = 0;
if (r >= 0) ret = r, rb = r;
else ret = -r, lb = r;
for (int i = 1; i <= N; ++i) {
if (A[i] == B[(((i + r) % N + N - 1) % N) + 1]) continue;
++ret;
if (lb <= L[i] || R[i] <= rb) continue;
dl[++K] = lb - L[i], dr[K] = R[i] - rb, p[K] = K;
}
std::sort(p + 1, p + K + 1, [](int i, int j) { return dl[i] < dl[j]; });
suf[K + 1] = 0;
for (int i = K; i >= 1; --i) suf[i] = std::max(suf[i + 1], dr[p[i]]);
int mnv = suf[1];
for (int i = 1, val = 0; i <= K; ++i)
val = std::max(val, dl[p[i]]),
mnv = std::min(mnv, val + suf[i + 1]);
return ret + 2 * mnv;
}
int main() {
scanf("%s%s", s + 1, t + 1), N = strlen(s + 1);
int ok = 0;
for (int i = 1; i <= N; ++i) if (t[i] == '1') ok = 1;
if (!ok) {
for (int i = 1; i <= N; ++i) if (s[i] == '1') return puts("-1"), 0;
return puts("0"), 0;
}
for (int i = 1; i <= N; ++i)
A[i] = s[i] - '0', B[i] = t[i] - '0';
for (int i = 1, x, y; i <= N; ++i) {
x = y = i;
while (!B[x]) --L[i], x = x > 1 ? x - 1 : N;
while (!B[y]) ++R[i], y = y < N ? y + 1 : 1;
}
int Ans = Inf;
for (int r = 1 - 2 * N; r <= 2 * N - 1; ++r)
Ans = std::min(Ans, Solve(r));
printf("%d\n", Ans);
return 0;
}
E - Shuffle and Swap
一個極其重要的轉化:
- 與其分別對 \(a\) 序列和 \(b\) 序列進行隨機排列,得到 \({(k!)}^2\) 種情況;
- 不如看作:先欽定 \(a\) 中的每個元素和 \(b\) 中的哪個元素配對,這里 \(k!\) 種情況,再確定每一對之間的順序,這里又 \(k!\) 種情況。
現在我們考慮欽定了配對的關系(也就是每次會把 \(A\) 中的哪兩個交換)后,統計合法的「每一對執行的先后順序」的數量。
我們考慮一張圖:只要是 \(a_i\) 與 \(b_j\) 配對了,這張圖中就從 \(a_i\) 向 \(b_j\) 連一條有向邊。
這張圖中有連邊的點,就是那些在 \(A\) 或 \(B\) 中為 \(1\) 的位置,如果均為 \(0\) 那肯定沒有邊與其相連了。
我們考慮如果一個位置 \(p\),如果 \(A_p, B_p\) 均為 \(1\),那么這個點在圖中就是出度入度均為 \(1\)。
如果 \(A_p = 1\) 但是 \(B_p = 0\) 的話,那就是只有出度為 \(1\),類似也有一些點入度為 \(1\)。
也就是說:拋開孤立點,這張圖其實是由若干個環和鏈構成的。
此時我們再來考慮「合法的『每一對執行的先后順序』的數量」應該如何計算。
對於環中的邊,誰先執行是完全無影響的,因為每次執行都是交換兩個 \(A\) 中的 \(1\) 罷了,沒有任何意義。
對於鏈中的邊,此時必須是要從鏈尾到鏈首的順序執行,只有唯一的一種執行方案。
我們考慮一下 \(A_p = 1\) 且 \(B_p = 0\) 的位置個數,假設為 \(e\),顯然 \(A_p = 0\) 且 \(B_p = 1\) 的位置個數也為 \(e\)。
也就是圖中會有 \(e\) 條鏈,鏈首和鏈尾的標號已經被欽定了,但是鏈的中轉節點和環上的點的標號還沒確定。
那么我們令鏈的標號被其起點確定,對每個起點欽定一個終點,乘一個 \(e!\) 到答案中。
那么我們按照標號順序確定每一條鏈中的點,然后再考慮若干個環中的點即可。
若一條鏈內部有 \(i\) 個點,那就有 \(i!\) 種排列方式,而且在 \(i + 1\) 條邊中,只有一種執行順序是合法的。
若一個環有 \(i\) 個點,就有 \((i - 1)!\) 種排列方式(圓排列),而且在 \(i\) 條邊中,\(i!\) 種執行順序都是合法的。
注意合並這些連通分量的時候,是需要乘多重組合數的,考慮用指數型生成函數的方法處理:
-
鏈的生成函數:\(\displaystyle \sum_{i = 0}^{\infty} \frac{i! \times 1 \times z^i}{i! \times (i + 1)!} = \sum_{i = 0}^{\infty} \frac{z^i}{(i + 1)!} = \frac{\mathrm{e}^z - 1}{z}\)。
-
環的生成函數:\(\displaystyle \sum_{i = 1}^{\infty} \frac{(i - 1)! \times i! \times z^i}{i! \times i!} = \sum_{i = 1}^{\infty} \frac{z^i}{i} = - \ln (1 - z)\)。
其中鏈之間是有序的,且必須取恰好 \(e\) 個,應該取 \(e\) 次冪。而環之間是無序的,數量也不限,應該取 \(\exp\)。
所以答案的生成函數應該是 \(\displaystyle \left( \frac{\mathrm{e}^z - 1}{z} \right)^e \exp(- \ln (1 - z)) = \frac{1}{1 - z} \left( \frac{\mathrm{e}^z - 1}{z} \right)^e\)。
而出度入度均為 \(1\) 的點的數量就是 \(k - e\) 個,所以最終答案(乘上之前的 \(e!\))即是 \(\displaystyle e! (k - e)! k! \left[ z^{k - e} \right] \frac{1}{1 - z} \left( \frac{\mathrm{e}^z - 1}{z} \right)^e\)。
可以使用多項式快速冪在 \(\mathcal O (N \log^2 N)\)(二進制拆分卷積快速冪)或 \(\mathcal O (N \log N)\)(多項式 \(\ln, \exp\))的時間內解決。
#include <cstdio>
#include <cstring>
#include <algorithm>
typedef long long LL;
const int Mod = 998244353;
const int G = 3, iG = 332748118;
const int MS = 1 << 15;
inline int qPow(int b, int e) {
int a = 1;
for (; e; e >>= 1, b = (LL)b * b % Mod)
if (e & 1) a = (LL)a * b % Mod;
return a;
}
inline int gInv(int b) { return qPow(b, Mod - 2); }
int Inv[MS], Fac[MS], iFac[MS];
inline void Init(int N) {
Fac[0] = 1;
for (int i = 1; i <= N; ++i) Fac[i] = (LL)Fac[i - 1] * i % Mod;
iFac[N] = gInv(Fac[N]);
for (int i = N; i >= 1; --i) iFac[i - 1] = (LL)iFac[i] * i % Mod;
for (int i = 1; i <= N; ++i) Inv[i] = (LL)Fac[i - 1] * iFac[i] % Mod;
}
inline int Binom(int N, int M) {
if (M < 0 || M > N) return 0;
return (LL)Fac[N] * iFac[M] % Mod * iFac[N - M] % Mod;
}
int Sz, InvSz, R[MS];
inline int getB(int N) { int Bt = 0; while (1 << Bt < N) ++Bt; return Bt; }
inline void InitFNTT(int N) {
int Bt = getB(N);
if (Sz == (1 << Bt)) return ;
Sz = 1 << Bt, InvSz = Mod - (Mod - 1) / Sz;
for (int i = 1; i < Sz; ++i) R[i] = R[i >> 1] >> 1 | (i & 1) << (Bt - 1);
}
inline void FNTT(int *A, int Ty) {
for (int i = 0; i < Sz; ++i) if (R[i] < i) std::swap(A[R[i]], A[i]);
for (int j = 1, j2 = 2; j < Sz; j <<= 1, j2 <<= 1) {
int wn = qPow(~Ty ? G : iG, (Mod - 1) / j2), w, X, Y;
for (int i = 0, k; i < Sz; i += j2) {
for (k = 0, w = 1; k < j; ++k, w = (LL)w * wn % Mod) {
X = A[i + k], Y = (LL)w * A[i + j + k] % Mod;
A[i + k] -= (A[i + k] = X + Y) >= Mod ? Mod : 0;
A[i + j + k] += (A[i + j + k] = X - Y) < 0 ? Mod : 0;
}
}
}
if (!~Ty) for (int i = 0; i < Sz; ++i) A[i] = (LL)A[i] * InvSz % Mod;
}
inline void PolyConv(int *_A, int N, int *_B, int M, int *_C, int tN = -1) {
if (tN == -1) tN = N + M - 1;
static int A[MS], B[MS];
InitFNTT(N + M - 1);
for (int i = 0; i < N; ++i) A[i] = _A[i];
for (int i = N; i < Sz; ++i) A[i] = 0;
for (int i = 0; i < M; ++i) B[i] = _B[i];
for (int i = M; i < Sz; ++i) B[i] = 0;
FNTT(A, 1), FNTT(B, 1);
for (int i = 0; i < Sz; ++i) A[i] = (LL)A[i] * B[i] % Mod;
FNTT(A, -1);
for (int i = 0; i < tN; ++i) _C[i] = A[i];
}
inline void PolyInv(int *_A, int N, int *_B) {
static int A[MS], B[MS], tA[MS], tB[MS];
for (int i = 0; i < N; ++i) A[i] = _A[i];
for (int i = N, Bt = getB(N); i < 1 << Bt; ++i) A[i] = 0;
B[0] = gInv(A[0]);
for (int L = 1; L < N; L <<= 1) {
int L2 = L << 1, L4 = L << 2;
InitFNTT(L4);
for (int i = 0; i < L2; ++i) tA[i] = A[i];
for (int i = L2; i < Sz; ++i) tA[i] = 0;
for (int i = 0; i < L; ++i) tB[i] = B[i];
for (int i = L; i < Sz; ++i) tB[i] = 0;
FNTT(tA, 1), FNTT(tB, 1);
for (int i = 0; i < Sz; ++i) tB[i] = tB[i] * (2 - (LL)tA[i] * tB[i] % Mod + Mod) % Mod;
FNTT(tB, -1);
for (int i = 0; i < L2; ++i) B[i] = tB[i];
}
for (int i = 0; i < N; ++i) _B[i] = B[i];
}
inline void PolyLn(int *_A, int N, int *_B) {
static int tA[MS], tB[MS];
for (int i = 1; i < N; ++i) tA[i - 1] = (LL)_A[i] * i % Mod;
PolyInv(_A, N - 1, tB);
InitFNTT(N + N - 3);
for (int i = N - 1; i < Sz; ++i) tA[i] = 0;
for (int i = N - 1; i < Sz; ++i) tB[i] = 0;
FNTT(tA, 1), FNTT(tB, 1);
for (int i = 0; i < Sz; ++i) tA[i] = (LL)tA[i] * tB[i] % Mod;
FNTT(tA, -1);
_B[0] = 0;
for (int i = 1; i < N; ++i) _B[i] = (LL)tA[i - 1] * Inv[i] % Mod;
}
inline void PolyExp(int *_A, int N, int *_B) {
static int A[MS], B[MS], tA[MS], tB[MS];
for (int i = 0; i < N; ++i) A[i] = _A[i];
for (int i = N, Bt = getB(N); i < 1 << Bt; ++i) A[i] = 0;
B[0] = 1;
for (int L = 1; L < N; L <<= 1) {
int L2 = L << 1, L4 = L << 2;
for (int i = L; i < L2; ++i) B[i] = 0;
PolyLn(B, L2, tA);
InitFNTT(L4);
for (int i = 0; i < L2; ++i) tA[i] = (!i + A[i] - tA[i] + Mod) % Mod;
for (int i = L2; i < Sz; ++i) tA[i] = 0;
for (int i = 0; i < L; ++i) tB[i] = B[i];
for (int i = L; i < Sz; ++i) tB[i] = 0;
FNTT(tA, 1), FNTT(tB, 1);
for (int i = 0; i < Sz; ++i) tA[i] = (LL)tA[i] * tB[i] % Mod;
FNTT(tA, -1);
for (int i = 0; i < L2; ++i) B[i] = tA[i];
}
for (int i = 0; i < N; ++i) _B[i] = B[i];
}
const int MN = 10005;
int N, k, e;
char A[MN], B[MN];
int F[MN];
int main() {
scanf("%s%s", A + 1, B + 1), N = strlen(A + 1);
for (int i = 1; i <= N; ++i) A[i] == '1' ? ++k, B[i] == '0' ? ++e : 0 : 0;
Init(k + 1);
for (int i = 0; i <= k - e; ++i) F[i] = iFac[i + 1];
PolyLn(F, k - e + 1, F);
for (int i = 0; i <= k - e; ++i) F[i] = (LL)F[i] * e % Mod;
PolyExp(F, k - e + 1, F);
int Ans = 0;
for (int i = 0; i <= k - e; ++i) Ans = (Ans + F[i]) % Mod;
printf("%lld\n", (LL)Ans * Fac[e] % Mod * Fac[k] % Mod * Fac[k - e] % Mod);
return 0;
}
F - Yes or No
我們將 \(\displaystyle \binom{N + M}{N}\) 種不同的答案序列,看成一條折線從 \((N, M)\) 走到 \((0, 0)\) 的方案數(每步只能向下或者向左走一格)。
最佳策略是:此時已知 Yes 有 \(a\) 個而 No 有 \(b\) 個,若 \(a > b\) 回答 Yes,若 \(a < b\) 回答 No,否則 \(a = b\) 回答 Yes/No 均可。
我們用橫坐標表示 \(a\),縱坐標表示 \(b\),下圖顯示的就是當 \(a = 13\) 且 \(b = 8\) 時的情況。
可以發現,此時若折線向左,就相當於是本題答案為 Yes,向下則本題答案為 No。
同時,圖中還有一條線:被方程 \(a = b\) 確定的斜線。
如果此時位置不在斜線上,則我們的最佳策略指出,將會回答更靠近斜線的答案。否則不妨回答 Yes。
如果回答的答案恰好與折線的走勢相同,則相當於答對了一題,否則答錯。
但是無論如何我們的位置,都會同折線一起前進一步,可能更靠近斜線也可能更遠離斜線。
那么此時就等價於:折線經過的紅色線段數量,就是我們會在這條折線對應的答案序列上產生的得分。
我們對所有的折線,將這個數值求和,然后除以 \(\displaystyle \binom{N + M}{N}\) 就是最終答案了。
折線經過的紅色線段數量如何統計呢?我們不妨假設 \(N \ge M\),也就是出發點在斜線下方的情況。
此時如果折線到達了斜線的上方,我們強行把到達斜線上方的那一部分,給折回來,折到斜線下方。
這樣一變換,折線在斜線上方的部分,經過的紅色線段的數量,可以發現大部分都是沒變化的。
但這只是除了剛好從斜線上往左走的那些,也就是,這樣折回來,損失了剛好從斜線上往左走的那一部分。
我們先不考慮那些損失,對於從未到達斜線上方的折線,顯然,經過的紅色線段數量恰好為 \(N\)!
那么我們只需統計剛好從斜線上往左走的期望次數。
顯然只需要對斜線上的每個點,統計斜線經過它的概率,乘上 \(1 / 2\)(往左走的概率)即可。
最終答案就是 \(N\) 加上這個「剛好從斜線上往左走的期望次數」。
#include <cstdio>
#include <algorithm>
typedef long long LL;
const int Mod = 998244353, Inv2 = (Mod + 1) / 2;
const int MN = 1000005;
inline int qPow(int b, int e) {
int a = 1;
for (; e; e >>= 1, b = (LL)b * b % Mod)
if (e & 1) a = (LL)a * b % Mod;
return a;
}
inline int gInv(int b) { return qPow(b, Mod - 2); }
int Fac[MN], iFac[MN];
inline void Init(int N) {
Fac[0] = 1;
for (int i = 1; i <= N; ++i) Fac[i] = (LL)Fac[i - 1] * i % Mod;
iFac[N] = gInv(Fac[N]);
for (int i = N; i >= 1; --i) iFac[i - 1] = (LL)iFac[i] * i % Mod;
}
inline int Binom(int N, int M) {
if (M < 0 || M > N) return 0;
return (LL)Fac[N] * iFac[M] % Mod * iFac[N - M] % Mod;
}
inline int Calc(int N, int M) {
return Binom(N + M, N);
}
int N, M;
int main() {
scanf("%d%d", &N, &M);
if (N < M) std::swap(N, M);
Init(N + M);
int Ans = 0;
for (int i = 1; i <= M; ++i) Ans = (Ans + (LL)Calc(i, i) * Calc(N - i, M - i)) % Mod;
Ans = (LL)Ans * gInv(Calc(N, M)) % Mod;
printf("%lld\n", (N + (LL)Ans * Inv2) % Mod);
return 0;
}