AtCoder Grand Contest 019


題目傳送門: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;
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM