1. 程式人生 > 實用技巧 >題解 CF1450H1 【Multithreading (Easy Version)】

題解 CF1450H1 【Multithreading (Easy Version)】

首先假設沒有 \(?\)

首先蒟蒻先給出一個很容易想到的貪心:

每一次選擇相鄰的兩個顏色相同的位置,然後把他們配對

如果配對不上,那麼現在的狀態一定就是 \(wbwbwbwb...\) ,需要有 \(\frac{cnt}{2}\) 個交點。\(cnt\)\(b\) 的個數。

於是整個問題的答案就是 \(|\frac{cnta - cntb}{2}|\)\(cnta\)\(b\) 在下標為奇數位置出現的次數,\(cntb\)\(b\) 在下標為偶數位置出現的次數。

如何證明這個是最小的?

首先可以讓整張圖同色的相交的線段變成不相交,答案嚴格不增。

考慮數學歸納法:

  • 一張空圖,或一張只有白色點的圖,答案 \(= 0 \ge |\frac{cnta - cntb}{2}|\)

  • 非空圖,首先找到兩個相連的黑點,且在他們在黑點中的位置相鄰(一定可以找到):

    • 兩個點一個在下標奇數處一個在下標偶數處:那麼兩個點一邊都是白點,可以自己匹配,另一邊的答案 \(\ge |\frac{(cnta - 1) - (cntb - 1)}{2}| = |\frac{cnta - cntb}{2}|\) 。於是成立。

    • 兩個點都在下標奇數處或都在下標偶數處:那麼兩個點一邊都是白點但是會多出一個點,那麼這個白點和其對應點會和現在找到的這條相連的邊產生一個交點。答案(不妨選中的是兩個下標奇數的點) \(\ge 1 + |\frac{(cnta - 2) - cntb}{2}| = 1 + |\frac{cnta - cntb}{2} - 1| \ge |\frac{cnta - cntb}{2} |\)

證畢。


統計現在已經填了的 \(b\) 奇數下標減偶數下標為 \(cnta\)\(?\) 在奇數下標的個數為 \(cntb\)\(?\) 在偶數下標的個數為 \(cntc\)

\[ans = \frac{1}{2^{cntb + cntc - 1}} \sum\limits_{i = 0}^{cntb} \sum\limits_{j = 0}^{cntc} |cnta + i - j| C_{cntb}^{i} C_{cntc}^{j} \]

\[= \frac{1}{2^{cntb + cntc - 1}} \sum\limits_{i = 0}^{cntb} \sum\limits_{j = 0}^{cntc} |cnta + i + (cntc - j)| C_{cntb}^{i} C_{cntc}^{j} \]

那麼這個東西就是可以卷積的了。

#include<bits/stdc++.h>
#define L(i, j, k) for(int i = j, i##E = k; i <= i##E; i++) 
#define R(i, j, k) for(int i = j, i##E = k; i >= i##E; i--)
#define ll long long
#define ull unsigned long long 
#define db long double
#define pii pair<int, int>
#define mkp make_pair
using namespace std;
const int N = 6e5 + 7;
const int mod = 998244353;
const int G = 3;
const int iG = (mod + 1) / G;
int qpow(int x, int y) {
	int res = 1;
	for(; y; x = 1ll * x * x % mod, y >>= 1) if(y & 1) res = 1ll * res * x % mod;
	return res;
}
int ny(int x) { return qpow(x, mod - 2); }
int pp[N];
void NTT(int *f, int len, int flag) {
	L(i, 0, len - 1) if(pp[i] < i) swap(f[pp[i]], f[i]);
	for(int i = 2; i <= len; i <<= 1) 
		for(int j = 0, l = (i >> 1), ch = qpow(flag == 1 ? G : iG, (mod - 1) / i); j < len; j += i) 
			for(int k = j, now = 1; k < j + l; k ++) {
				int pa = f[k], pb = 1ll * f[k + l] * now % mod;
				f[k] = (pa + pb) % mod, f[k + l] = (pa - pb + mod) % mod, now = 1ll * now * ch % mod;
			}
	if(flag == -1) {
		int nylen = ny(len);
		L(i, 0, len - 1) f[i] = 1ll * f[i] * nylen % mod;
	}
}
void George1123(int *f, int *g, int *ans, int lena, int lenb) { // Tornado king
	int lim; for(lim = 1; lim <= lena + lenb; lim <<= 1);
	L(i, 0, lim - 1) pp[i] = ((pp[i >> 1] >> 1) | ((i & 1) * (lim >> 1)));
	NTT(f, lim, 1), NTT(g, lim, 1);
	L(i, 0, lim - 1) ans[i] = 1ll * f[i] * g[i] % mod;
	NTT(ans, lim, -1);
}
int n, m, cnta, cntb, cntc, jc[N], njc[N], ans, f[N], g[N], h[N];
int C(int x, int y) { return 1ll * jc[x] * njc[y] % mod * njc[x - y] % mod; }
char s[N];
int main() {
	scanf("%d%d", &n, &m);
	scanf("%s", s + 1);
	jc[0] = njc[0] = 1;
	L(i, 1, n) jc[i] = 1ll * jc[i - 1] * i % mod, njc[i] = ny(jc[i]);
	L(i, 1, n) {
		if(s[i] == 'b') {
			if(i % 2) cnta ++;
			else cnta --;
		}
		else if(s[i] == '?') {
			if(i % 2) cntb ++;
			else cntc ++;
		}
	}
	L(i, 0, cntb) f[i] = C(cntb, i);
	L(i, 0, cntc) g[i] = C(cntc, i);
	George1123(f, g, h, cntb, cntc);
	L(i, 0, cntb + cntc) if((cnta + i - cntc) % 2 == 0) (ans += 1ll * abs(cnta + i - cntc) / 2 * h[i] % mod) %= mod;
	ans = 1ll * ans * qpow(ny(2), cntb + cntc - 1) % mod;
	printf("%d\n", ans);
	return 0;
}

現在已經可以通過 \(H1\) 了,但是給一種更好寫,為 \(H2\) 做基礎的方法:

考慮如何優化得到的卷積式 \(f_t = \sum\limits_{i = 0}^{a} C_{a}^i \sum\limits_{j = 0}^{b}C_{b}^{j} [i + j = t]\)

觀察規律發現 \(f_t = C_{a+b}^t\) ,如何證明呢?

考慮其組合意義。\(C_{a+b}^t\) 相當於從 \(a + b\) 個球中取出 \(t\) 個,那麼我們可以列舉從前 \(a\) 個取出的個數和前 \(b\) 個取出的個數,也就是 \(\sum\limits_{i = 0}^{a} C_{a}^i \sum\limits_{j = 0}^{b}C_{b}^{j} [i + j = t]\)

於是答案就是 \(\frac{1}{2^{cntb + cntc}} \sum\limits_{i = 0, i \equiv cnta - cntc \pmod 2}^{cnta + cntb} |cnta - cntc + i| C_{cntb + cntc}^{i}\)

這個式子看起來很不舒服,讓 \(cnt = cntb + cntc, t = cntc - cnta\)

\(ans = \frac{1}{2^{cnt}} \sum\limits_{i = 0, i \equiv t \pmod 2}^{cnt} |i - t| C_{cnt}^{i}\)

所以我們得到了更簡潔的程式碼:

#include<bits/stdc++.h>
#define L(i, j, k) for(int i = j, i##E = k; i <= i##E; i++) 
#define R(i, j, k) for(int i = j, i##E = k; i >= i##E; i--)
#define ll long long
#define ull unsigned long long 
#define db long double
#define pii pair<int, int>
#define mkp make_pair
using namespace std;
const int N = 2e5 + 7;
const int mod = 998244353;
int qpow(int x, int y) {
	int res = 1;
	for(; y; x = 1ll * x * x % mod, y >>= 1) if(y & 1) res = 1ll * res * x % mod;
	return res;
}
int ny(int x) { return qpow(x, mod - 2); }
int n, m, t, cnt, jc[N], njc[N], ans;
int C(int x, int y) { return 1ll * jc[x] * njc[y] % mod * njc[x - y] % mod; }
char s[N];
int main() {
	scanf("%d%d", &n, &m);
	scanf("%s", s + 1);
	jc[0] = njc[0] = 1;
	L(i, 1, n) jc[i] = 1ll * jc[i - 1] * i % mod, njc[i] = ny(jc[i]);
	L(i, 1, n) {
		if(s[i] == 'b') {
			if(i % 2) t --;
			else t ++;
		}
		else if(s[i] == '?') {
			cnt ++;
			if(i % 2 == 0) t ++;
		}
	}
	L(i, 0, cnt) if((t + i) % 2 == 0) (ans += 1ll * abs(t - i) * C(cnt, i) % mod) %= mod;
	ans = 1ll * ans * qpow(ny(2), cnt) % mod;
	printf("%d\n", ans);
	return 0;
}