1. 程式人生 > 實用技巧 >Codeforces Global Round 12 題解

Codeforces Global Round 12 題解

原題解

A

題意

給定一個長度為 \(n\) 的字串,重新排列字元,使得其不包含 trygub 作為子序列。

\(1\le n\le 200\),多測,資料組數不超過 \(100\)

Solution

把所有字母排序即可。

Code

#include <bits/stdc++.h>

template <class T>
inline void read(T &res)
{
	res = 0; bool bo = 0; char c;
	while (((c = getchar()) < '0' || c > '9') && c != '-');
	if (c == '-') bo = 1; else res = c - 48;
	while ((c = getchar()) >= '0' && c <= '9')
		res = (res << 3) + (res << 1) + (c - 48);
	if (bo) res = ~res + 1;
}

const int N = 205;

int n;
char s[N];

void work()
{
	read(n); scanf("%s", s + 1);
	std::sort(s + 1, s + n + 1);
	for (int i = 1; i <= n; i++) putchar(s[i]);
	puts("");
}

int main()
{
	int T; read(T);
	while (T--) work();
	return 0;
}

B

題意

給定 \(n\) 個座標互不相同的點 \((x_i,y_i)\) 和一個引數 \(k\),每次可以選一個點 \((x,y)\),把與 \((x,y)\) 曼哈頓距離不超過 \(k\) 的所有點座標都改成 \((x,y)\)

求讓所有點座標相同的最少運算元或告知無解。

\(2\le n\le 100\)\(0\le k\le10^6\)\(0\le x_i,y_i\le 10^5\),多測,資料組數不超過 \(100\)

Solution

對一個點操作之後這個點到其他點的曼哈頓距離都會大於 \(k\),所以只有 \(1\)\(-1\) 兩種答案。

直接模擬即可,\(O(tn^2)\)

Solution

#include <bits/stdc++.h>

template <class T>
inline void read(T &res)
{
	res = 0; bool bo = 0; char c;
	while (((c = getchar()) < '0' || c > '9') && c != '-');
	if (c == '-') bo = 1; else res = c - 48;
	while ((c = getchar()) >= '0' && c <= '9')
		res = (res << 3) + (res << 1) + (c - 48);
	if (bo) res = ~res + 1;
}

const int N = 105;

int n, k, x[N], y[N];

void work()
{
	read(n); read(k);
	for (int i = 1; i <= n; i++) read(x[i]), read(y[i]);
	for (int i = 1; i <= n; i++)
	{
		bool is = 1;
		for (int j = 1; j <= n; j++)
			if (abs(x[i] - x[j]) + abs(y[i] - y[j]) > k) is = 0;
		if (is) return (void) puts("1");
	}
	puts("-1");
}

int main()
{
	int T; read(T);
	while (T--) work();
	return 0;
}

C1 & C2

題意

給定一個 \(n\times n\) 的含 XO. 的棋盤,改動一些不為 . 的位置(不能改成 .),使得沒有橫向或縱向連續 \(3\) 個都為 XO

如果不為 . 的位置有 \(k\) 個,那麼你要給出一個改動不超過 \(\lfloor\frac k3\rfloor\) 的方案。

\(1\le n\le 300\),多測,資料組數不超過 \(100\)

對於簡單版,初始棋盤沒有 O

Solution

考慮構造一個模板:\((i,j)\)O 當且僅當 \(i+j\equiv x(\bmod 3)\)\(x\in\{0,1,2\}\),顯然這樣是合法的。

這樣對於簡單版就可以選擇一個改動次數最少的 \(x\) 進行求答案了。

對於困難版,考慮這個模板的擴充套件:如果原棋盤上 \((i,j)\)X,則 \((i,j)\)O 當且僅當 \(i+j\equiv x(\bmod 3)\),否則 \((i,j)\)X 當且僅當 \(i+j\equiv x+1(\bmod 3)\)

同樣地,可以證明這個模板合法,並且 \(x\) 依次取 \(0,1,2\) 改動的位置數之和為 \(k\),可以選一個改動位置數最小的。

\(O(tn^2)\)

Solution

#include <bits/stdc++.h>

template <class T>
inline void read(T &res)
{
	res = 0; bool bo = 0; char c;
	while (((c = getchar()) < '0' || c > '9') && c != '-');
	if (c == '-') bo = 1; else res = c - 48;
	while ((c = getchar()) >= '0' && c <= '9')
		res = (res << 3) + (res << 1) + (c - 48);
	if (bo) res = ~res + 1;
}

const int N = 305;

int n;
char s[N][N], f[N][N];

void work()
{
	int cnt = 0;
	read(n);
	for (int i = 1; i <= n; i++) scanf("%s", s[i] + 1);
	for (int i = 1; i <= n; i++) for (int j = 1; j <= n; j++)
		cnt += s[i][j] != '.';
	for (int t = 0; t < 3; t++)
	{
		int cc = 0;
		for (int i = 1; i <= n; i++)
			for (int j = 1; j <= n; j++)
			{
				char c;
				if (s[i][j] == '.') c = '.';
				else if (s[i][j] == 'X') c = (i + j + t) % 3 ? 'X' : 'O';
				else c = (i + j + t + 1) % 3 ? 'O' : 'X';
				if (c != s[i][j]) cc++; f[i][j] = c;
			}
		if (cc <= cnt / 3)
		{
			for (int i = 1; i <= n; i++)
			{
				for (int j = 1; j <= n; j++) putchar(f[i][j]);
				puts("");
			}
			return;
		}
	}
}

int main()
{
	int T; read(T);
	while (T--) work();
	return 0;
}

D

題意

給定一個長度為 \(n\) 的數列 \(a\),對於所有 \(1\le i\le n\),判斷數列所有長度為 \(i\) 的區間最小值是否構成一個 \(1\)\(n-i+1\) 的排列。

\(1\le n\le 3\times10^5\)\(1\le a_i\le n\),多測,資料組數不超過 \(10^4\),所有資料的 \(n\) 之和不超過 \(3\times10^5\)

Solution

\(i=1\)\(n\) 特殊處理。

否則條件可以表示成:唯一的 \(1\) 必須在數列兩端,刪掉這個 \(1\) 之後,唯一的 \(2\) 必須在數列兩端,刪掉這個 \(2\) 之後,唯一的 \(3\) 必須在數列兩端,以此類推,最後得到的一個長度為 \(i\) 的區間最小值為 \(n-i+1\)

於是可以做到 \(O(\sum n)\)

Code

#include <bits/stdc++.h>

template <class T>
inline void read(T &res)
{
	res = 0; bool bo = 0; char c;
	while (((c = getchar()) < '0' || c > '9') && c != '-');
	if (c == '-') bo = 1; else res = c - 48;
	while ((c = getchar()) >= '0' && c <= '9')
		res = (res << 3) + (res << 1) + (c - 48);
	if (bo) res = ~res + 1;
}

template <class T>
inline T Min(const T &a, const T &b) {return a < b ? a : b;}

const int N = 3e5 + 5;

int n, a[N], L[N], R[N], cnt[N];
bool is[N], ans[N];

void work()
{
	read(n);
	for (int i = 1; i <= n; i++) is[i] = ans[i] = 0, cnt[i] = 0;
	for (int i = 1; i <= n; i++) read(a[i]), is[a[i]] = 1, cnt[a[i]]++;
	ans[n] = is[1]; ans[1] = 1;
	for (int i = 1; i <= n; i++) if (!is[i]) ans[1] = 0;
	for (int i = 1, l = 1, r = n; i < n; i++)
	{
		if (cnt[i] != 1 || (a[l] != i && a[r] != i)) break;
		ans[n - i] = 1; if (a[l] == i) l++; else r--;
		L[n - i] = l; R[n - i] = r;
	}
	for (int i = 2, cur = n + 1; i < n; i++)
		if (ans[i])
		{
			if (cur == n + 1)
				for (int j = L[i]; j <= R[i]; j++) cur = Min(cur, a[j]);
			else cur = Min(cur, Min(a[L[i]], a[R[i]]));
			if (cur != n - i + 1) ans[i] = 0;
		}
	for (int i = 1; i <= n; i++) printf("%d", ans[i]);
	puts("");
}

int main()
{
	int T; read(T);
	while (T--) work();
	return 0;
}

E

題意

給定一個 \(n\) 個點 \(m\) 條邊的連通無向圖,你需要為每一條邊定向。再為每個點定權值,使得每條邊終點的權值等於起點權值 \(+1\)

同時你需要最大化最大權值與最小權值之差,並給出一組解。部分邊的方向已經欽定,無解輸出 \(-1\)

\(1\le n\le 200\)\(n-1\le m\le 2000\)

Solution

考慮把條件 \(a_i-a_j\in\{-1,1\}\) 擴充成 \(-1\le a_i-a_j\le 1\),就轉化成了差分約束問題,如果一條邊被欽定為 \(i\)\(j\) 則直接 \(1\le a_j-a_i\le 1\)

Floyd 判負環輸出 \(-1\) 之後列舉一個點 \(i\) 為源點,令 \(a_j\)\(i\)\(j\) 的最短路,選一個能使得 \(a\) 極差最大的 \(u\) 作為答案即可。

之前沒有考慮到 \(a_i-a_j=0\) 的影響,但實際上上面這個帶權圖建出來之後,如果原圖是二分圖則固定源點 \(i\) 之後,\(j\) 的最短路奇偶性就等於 \(j\) 所屬的二分圖部,自然就不會出現 \(a_i-a_j=0\) 的情況,而原圖不是二分圖時是無解的。

\(O(n^3)\)

Code

#include <bits/stdc++.h>

template <class T>
inline void read(T &res)
{
	res = 0; bool bo = 0; char c;
	while (((c = getchar()) < '0' || c > '9') && c != '-');
	if (c == '-') bo = 1; else res = c - 48;
	while ((c = getchar()) >= '0' && c <= '9')
		res = (res << 3) + (res << 1) + (c - 48);
	if (bo) res = ~res + 1;
}

template <class T>
inline T Min(const T &a, const T &b) {return a < b ? a : b;}

template <class T>
inline T Max(const T &a, const T &b) {return a > b ? a : b;}

const int N = 205, M = 4005, INF = 0x3f3f3f3f;

int n, m, ecnt, nxt[M], adj[N], go[M], val[M], f[N][N], res[N], o[N], OO = -1;
bool vis[N], col[N];

void add_edge(int u, int v)
{
	nxt[++ecnt] = adj[u]; adj[u] = ecnt; go[ecnt] = v;
	nxt[++ecnt] = adj[v]; adj[v] = ecnt; go[ecnt] = u;
}

void dfs(int u)
{
	vis[u] = 1;
	for (int e = adj[u], v = go[e]; e; e = nxt[e], v = go[e])
		if (!vis[v]) col[v] = col[u] ^ 1, dfs(v);
		else if (col[v] == col[u]) {puts("NO"); exit(0);}
}

int main()
{
	memset(f, INF, sizeof(f));
	int x, y, z;
	read(n); read(m);
	for (int i = 1; i <= n; i++) f[i][i] = 0;
	while (m--)
	{
		read(x); read(y); read(z); add_edge(x, y);
		if (!z) f[x][y] = f[y][x] = 1;
		else f[x][y] = 1, f[y][x] = -1;
	}
	dfs(1);
	for (int k = 1; k <= n; k++)
	{
		for (int i = 1; i <= n; i++)
			for (int j = 1; j <= n; j++)
				if (1ll * f[i][k] + f[k][j] + f[j][i] < 0)
					return (void) puts("NO"), 0;
		for (int i = 1; i <= n; i++)
			for (int j = 1; j <= n; j++)
				f[i][j] = Min(f[i][j], f[i][k] + f[k][j]);
	}
	for (int i = 1; i <= n; i++)
	{
		int mn = 0, mx = 0;
		for (int j = 1; j <= n; j++)
			mn = Min(mn, f[i][j]), mx = Max(mx, f[i][j]);
		OO = Max(OO, mx - mn); res[i] = mx - mn; o[i] = mn;
	}
	printf("YES\n%d\n", OO);
	for (int i = 1; i <= n; i++) if (res[i] == OO)
	{
		for (int j = 1; j <= n; j++) printf("%d ", f[i][j] - o[i]);
		puts(""); return 0;
	}
	return 0;
}

F

題意

給定長度為 \(n\) 的數列 \(a\),求一個排列 \(p\),使得對於所有 \(1\le i<n\) 都有 \(a_{p_i}\ne a_{p_{i+1}}\) 且最小化 \(\sum_{i=1}^{n-1}[|p_i-p_{i+1}|>1]\)

\(1\le n\le 10^5\)\(1\le a_i\le n\),多測,資料組數不超過 \(10^4\),所有資料 \(n\) 之和不超過 \(10^5\)

Solution

問題轉化成求一個最小的 \(k\),使得數列能被分成 \(k\) 段(相鄰相等的數之間必須分段),這些段能夠以任意順序拼接起來(可翻轉不分段),使得任意介面處的兩個數不同。

先考慮原問題有解的條件:出現最多的那種數出現次數 \(c\) 滿足 \(2c\le n+1\)

把每段看成一個無序二元組 \((x,y)\),要把 \(k\) 個二元組拼接起來,使得對於拼接處有 \(y_i\ne x_{i+1}\),我們有個類似的結論:

有解當且僅當這 \(2k\) 個數中出現最多的那種數出現次數 \(c\) 滿足 \(c\le k+1\)

證明可以歸納:

如果 \(c\le k\) 且存在一個二元組的 \(x\ne y\),則可以把這個二元組之外的 \(k-1\) 個二元組先接好之後再接上 \((x,y)\),注意到這裡 \(x\)\(y\) 一定有一端能接上。
如果 \(c\le k\) 且所有二元組都有 \(x=y\),則這就相當於把 \(k\) 個數拼接起來並且出現次數最多的那種數出現了不超過 \(\lfloor\frac k2\rfloor\) 次,這時一定有解。
如果 \(c=k+1\),設出現 \(k+1\) 次的數為 \(u\),則可以計算出如果有 \(m\)\((u,u)\) 就有 \(m-1\)\((\ne u,\ne u)\),這時候可以讓 \((u,u)\)\((\ne u,\ne u)\) 交替排列,然後把所有的 \((\ne u,u)\) 都接到後面,就達到了目的。

回到問題,先把所有的相鄰相同的數之間都分割一下,如果已經滿足了 \(c\le k+1\) 則已經得到答案。

否則設出現 \(k+1\) 次的數為 \(u\),依次考慮每個 \((\ne u,\ne u)\) 的相鄰數對,在其中分割一下,直到 \(c\le k+1\) 為止,如果所有合法相鄰數對都用完了還不能滿足就無解。

\(O(\sum n)\)

Code

#include <bits/stdc++.h>

template <class T>
inline void read(T &res)
{
	res = 0; bool bo = 0; char c;
	while (((c = getchar()) < '0' || c > '9') && c != '-');
	if (c == '-') bo = 1; else res = c - 48;
	while ((c = getchar()) >= '0' && c <= '9')
		res = (res << 3) + (res << 1) + (c - 48);
	if (bo) res = ~res + 1;
}

const int N = 1e5 + 5;

int n, a[N], cnt[N];

void work()
{
	int ans = 0;
	read(n);
	for (int i = 1; i <= n; i++) read(a[i]), cnt[i] = 0;
	cnt[a[1]]++; cnt[a[n]]++;
	for (int i = 1; i < n; i++) if (a[i] == a[i + 1]) cnt[a[i]] += 2, ans++;
	int mx = 0, id = -1;
	for (int i = 1; i <= n; i++) if (cnt[i] > mx) mx = cnt[i], id = i;
	if (mx <= ans + 2) return (void) printf("%d\n", ans);
	for (int i = 1; i < n; i++)
		if (a[i] != id && a[i + 1] != id && a[i] != a[i + 1])
		{
			ans++;
			if (mx <= ans + 2) return (void) printf("%d\n", ans);
		}
	puts("-1");
}

int main()
{
	int T; read(T);
	while (T--) work();
	return 0;
}

G

題意

給定一個長度為 \(n\),字符集為 \(20\) 的字串,每次操作可以選擇兩個個字元 \(x\ne y\),滿足 \(x\)\(y\) 都在現在的串中出現過,且如果字元 \(x\) 出現的位置從小到大依次為 \(i_1,i_2,\dots,i_m\),則需要滿足 \(\frac ab\times (i_m-i_1+1)\le m\),把所有的字元 \(x\) 改成 \(y\)

求出所有的字元 \(x\),滿足可以通過若干次操作使得所有的字元都變成 \(c\)

\(1\le n\le 5000\)\(1\le a\le b\le 10^5\)

Solution

把一些字符合併成一個之後,這個字元可以用一個三元組 \((l,r,m)\) 表示,即出現的第一個和最後一個位置分別為 \(l\)\(r\),出現了 \(m\) 次。

結論:對於兩個字元 \((l_1,r_1,m_1)\)\((l_2,r_2,m_2)\),如果 \([l_1,r_1]\)\([l_2,r_2]\) 有交,且這兩個字元都滿足 \(\frac ab\times (r-l+1)\le m\),則這兩個字符合並後仍然滿足。
證明:在所給的條件下,合併之後 \(m\) 等於 \(m_1\)\(m_2\) 之和,\(r-l+1\) 小於 \(r_1-l_1+1\)\(r_2-l_2+1\) 之和,易證。

回到原問題,設 \(f_S\) 表示字元集合 \(S\) 能否被合併成一個,\(g_S\) 表示字元集合 \(S\) 能否被合併成若干個滿足 \(\frac ab\times (r-l+1)\le m\) 的字元。

不難發現把所有的字元都變成 \(c\),相當於把除 \(c\) 以外的所有字元都合併成若干個滿足 \(\frac ab\times (r-l+1)\le m\) 的字元之後再一一合併到 \(c\) 上。

所以 \(f_S=\bigcup_{c\in S}g_{S-c}\),最終答案所有字元都能變成 \(c\) 當且僅當 \(g_{\Sigma-c}=true\)

考慮 \(g\) 的轉移,由之前的性質得到,\(S\) 合併成的字元區間兩兩不交。

到這裡我們不難發現可以把 \(S\) 中的區間劃分成若干個連通塊,滿足屬於不同連通塊的區間不交,求出這些連通塊內部字元的區間並。

這時候可以注意到,如果把這些連通塊從左到右排列,那麼 \(S\) 中的字符合併成的每個字元一定對應了連續的幾個連通塊。

轉化成段的劃分問題,這時候就可以把原來的子集列舉改成分段 DP,\(O(|\Sigma|^2)\) 解決。

總複雜度 \(O(n+2^{|\Sigma|}|\Sigma|^2)\)

Code

#include <bits/stdc++.h>

template <class T>
inline T Min(const T &a, const T &b) {return a < b ? a : b;}

template <class T>
inline T Max(const T &a, const T &b) {return a > b ? a : b;}

const int N = 5005, E = 22, C = (1 << 20) + 5;

int n, a, b, m, bel[300], l[E], r[E], sze[E], tot, o[E], st[E], cnt, w;
bool is[300], f[C], g[C], h[C], fl[E];
char s[N], ch[E];

int main()
{
	std::cin >> n >> a >> b;
	scanf("%s", s + 1);
	for (int i = 1; i <= n; i++) is[s[i]] = 1;
	for (char c = 'a'; c <= 'z'; c++) if (is[c]) ch[bel[c] = ++m] = c;
	for (int i = 1; i <= n; i++) r[bel[s[i]]] = i, sze[bel[s[i]]]++;
	for (int i = n; i >= 1; i--) l[bel[s[i]]] = i;
	f[0] = g[0] = 1;
	for (int S = 1; S < (1 << m); S++)
	{
		tot = w = 0;
		for (int i = 1; i <= m; i++)
			if ((S >> i - 1) & 1) o[++tot] = i,
				f[S] |= g[S ^ (1 << i - 1)];
		std::sort(o + 1, o + tot + 1, [&](int x, int y)
			{return r[x] < r[y];});
		int lt = 9973, rt = -9973, sum = 0;
		for (int i = tot, mn = 9973; i >= 1; i--)
		{
			if (r[o[i]] < mn) st[++w] = 1 << o[i] - 1;
			else st[w] |= 1 << o[i] - 1;
			mn = Min(mn, l[o[i]]);
			lt = Min(lt, l[o[i]]); rt = Max(rt, r[o[i]]);
			sum += sze[o[i]];
		}
		h[S] = a * (rt - lt + 1) <= sum * b; fl[0] = 1;
		for (int i = 1; i <= w; i++)
		{
			fl[i] = 0;
			for (int j = i, t = st[i]; j >= 1; j--, t |= st[j])
				if (fl[j - 1] && f[t] && h[t])
					{fl[i] = 1; break;}
		}
		g[S] = fl[w];
	}
	for (int c = 1; c <= m; c++) if (g[(1 << m) - 1 ^ (1 << c - 1)]) cnt++;
	std::cout << cnt << " ";
	for (int c = 1; c <= m; c++)
		if (g[(1 << m) - 1 ^ (1 << c - 1)])
			putchar(ch[c]), putchar(' ');
	return puts(""), 0;
}

H1 & H2

題意

有一個長度為 \(n\)(偶數)的環,環上每個點可以為黑色或白色,黑色和白色的點個數都是偶數。

同色的點之間可以連邊,邊的顏色和這兩個點的顏色相同。你需要找到一組匹配,使得異色且相交的邊的對數儘可能少。

你有一個長度為 \(n\) 的字串 \(s\),包含 b(黑色)、w(白色),?(未知)來描述這個環,且至少有一個 ?。你需要求出在 ? 的顏色任意為黑色和白色(需要滿足黑色和白色的點數都為偶數)的情況下,異色且相交的邊的對數最小值的期望。

除此之外,有 \(m\) 次修改,每次會修改 \(s\) 的一個下標,然後需要再次回答。

\(2\le n\le2\times10^5\)\(0\le m\le2\times10^5\),對於簡單版 \(m=0\)

Solution

結論 \(1\):相鄰的兩個同色點直接消除掉一定是最優方案。
證明:設 \(i\) 原來連了 \(j\)\(i+1\) 原來連了 \(k\),那麼 \((i,i+1)\)\(j\)\(k\) 把環上剩下的點劃分成了三個部分,畫個圖討論一下可以發現無論另一條邊的起點和終點分別在哪個部分內,把 \((i,j)(i+1,k)\) 換成 \((i,i+1)(j,k)\) 都不會讓那條邊與 \(i,i+1,j,k\) 相交的次數變多,得證。

由這個結論可以推出,如果斷環為鏈,維護一個棧,依次把顏色加入棧中,判斷如果與棧頂相同就彈棧,否則進棧,最後棧中會剩下 \(k\) 個點,它們的顏色黑白交錯排列,不難得出最小相交次數即為 \(\frac k4\)

結論 \(2\):設 \(even_b\)\(even_w\)\(odd_b\)\(odd_w\) 分別表示偶位和奇位上黑色和白色點的個數,則最終棧中剩下的點數為 \(|even_b+odd_w-even_w-odd_b|\)
證明:考慮強行欽定每種顏色是進棧還是出棧,注意到如果 \(even_b+odd_w\ge even_w+odd_b\),我們可以把偶位上的黑點和奇位上的白點視為進棧(\(1\)),其他視為退棧(\(-1\)),這時候只要滿足任何時候棧容量(字首和)不為負數即可。而一個 \(1\) 不比 \(-1\) 少的數列一定存在一個迴圈位移滿足任意字首和不為負,所以這時候一定能找到一個合適的位置斷環,然後依次對棧進行操作,不難發現棧底的顏色是不變的,並且棧大小一定與當前已操作個數擁有相同的奇偶性,就證明了這麼欽定進出棧的正確性,\(even_b+odd_w<even_w+odd_b\) 也一樣。

進一步地,\(|even_b+odd_w-even_w-odd_b|\) 可以寫成 \(|2odd_w+2even_b-n|\),列舉 \(i=odd_w+even_b\) 滿足 \(2odd_w+2even_b-n\equiv0(\bmod 4)\),顯然滿足 \(odd_w+even_b=i\) 的方案數是一個組合數,可以直接計算,這樣可過簡單版。

對於複雜版,考慮這個組合數和式的通式:

\[\sum_{i=0}^z\binom zi|i-x|[i\bmod 2=y] \]

考慮大力分 \(i\le x\)\(i>x\)\(\binom zi\)\(i\binom zi\) 的和分開維護,\([i\bmod 2=y]\) 可用單位根反演等技巧搞掉。

注意到一次修改只會讓 \(z\)\(x\)\(1\) 或減 \(1\),對於 \(z\) 加一之後組合數字首和的變化,我們根據組合數遞推公式有:

\[\sum_{i=0}^x\binom {z+1}i=2\sum_{i=0}^x\binom zi-\binom zx \]

這樣就實現了組合數字首和在 \(z\) 加一的過程中的變化,要維護的其他值推導方式類似。

\(O(n+m)\)

Code

#include <bits/stdc++.h>

template <class T>
inline void read(T &res)
{
	res = 0; bool bo = 0; char c;
	while (((c = getchar()) < '0' || c > '9') && c != '-');
	if (c == '-') bo = 1; else res = c - 48;
	while ((c = getchar()) >= '0' && c <= '9')
		res = (res << 3) + (res << 1) + (c - 48);
	if (bo) res = ~res + 1;
}

const int N = 2e5 + 5, EI = 998244353, I2 = 499122177;

int n, m, fac[N], inv[N], ans, cnt, cnt0, cnt1, cnt_0, cnt_1, p2[N], i2[N],
posC, posCi, negC, negCi;
char s[N];

int C(int n, int m)
{
	if (m < 0 || m > n) return 0;
	return 1ll * fac[n] * inv[m] % EI * inv[n - m] % EI;
}

int main()
{
	int pos; char c; read(n); read(m);
	fac[0] = inv[0] = inv[1] = p2[0] = i2[0] = 1;
	for (int i = 1; i <= n; i++) fac[i] = 1ll * fac[i - 1] * i % EI,
		p2[i] = 2ll * p2[i - 1] % EI, i2[i] = 1ll * I2 * i2[i - 1] % EI;
	for (int i = 2; i <= n; i++) inv[i] = 1ll * (EI - EI / i) * inv[EI % i] % EI;
	for (int i = 2; i <= n; i++) inv[i] = 1ll * inv[i] * inv[i - 1] % EI;
	scanf("%s", s + 1);
	for (int i = 1; i <= n; i++)
	{
		if (s[i] == '?') cnt++;
		if ((i & 1) && s[i] == 'w') cnt0++;
		if ((i & 1) && s[i] != 'b') cnt_0++;
		if (!(i & 1) && s[i] == 'b') cnt1++;
		if (!(i & 1) && s[i] != 'w') cnt_1++;
	}
	for (int i = 0; i <= n; i++) if ((2 * i - n) % 4 == 0)
	{
		if (cnt0 + cnt1 > i || i > cnt_0 + cnt_1) continue;
		ans = (1ll * abs(2 * i - n) / 4 * C(cnt_0 + cnt_1
			- cnt0 - cnt1, i - cnt0 - cnt1) + ans) % EI;
	}
	std::cout << 1ll * ans * i2[cnt - 1] % EI << std::endl;
	for (int i = 0; i <= cnt_0 + cnt_1 - cnt0 - cnt1 &&
		i <= n / 2 - cnt0 - cnt1; i++)
		{
			int d = C(cnt_0 + cnt_1 - cnt0 - cnt1, i),
				di = 1ll * d * i % EI;
			posC = (posC + d) % EI; posCi = (posCi + di) % EI;
			if (i & 1) d = EI - d, di = EI - di;
			negC = (negC + d) % EI; negCi = (negCi + di) % EI;
		}
	while (m--)
	{
		read(pos);
		while ((c = getchar()) != 'w' && c != 'b' && c != '?');
		int mp = cnt_0 + cnt_1 - cnt0 - cnt1, mq = n / 2 - cnt0 - cnt1;
		if (pos & 1) cnt0 -= s[pos] == 'w', cnt0 += c == 'w',
			cnt_0 -= s[pos] != 'b', cnt_0 += c != 'b';
		else cnt1 -= s[pos] == 'b', cnt1 += c == 'b',
			cnt_1 -= s[pos] != 'w', cnt_1 += c != 'w';
		cnt -= s[pos] == '?'; cnt += c == '?';
		s[pos] = c;
		int np = cnt_0 + cnt_1 - cnt0 - cnt1, nq = n / 2 - cnt0 - cnt1;
		if (mp < np)
		{
			int t = C(mp, mq), u = mq & 1 ? EI - t : t;
			posCi = (2ll * posCi - 1ll * (mq + 1) * t % EI
				+ EI + posC) % EI;
			posC = (2ll * posC - t + EI) % EI;
			negCi = (1ll * (mq + 1) * u - negC + EI) % EI;
			negC = u;
		}
		else if (mp > np)
		{
			posC = 1ll * I2 * (C(np, mq) + posC) % EI;
			posCi = (1ll * (mq + 1) * C(np, mq) + posCi - posC + EI)
				% EI * I2 % EI;
			if (np)
			{
				int u = mq & 1 ? EI - C(np - 1, mq) : C(np - 1, mq);
				negC = u;
				negCi = (1ll * (mq + 1) * u - (np > 1 ? (mq & 1 ?
					EI - C(np - 2, mq) : C(np - 2, mq))
						: (mq >= 0)) + EI) % EI;
			}
			else negC = mq >= 0, negCi = 0;
		}
		if (mq < nq)
		{
			int d = C(np, nq), di = 1ll * d * nq % EI;
			posC = (posC + d) % EI; posCi = (posCi + di) % EI;
			if (nq & 1) d = EI - d, di = EI - di;
			negC = (negC + d) % EI; negCi = (negCi + di) % EI;
		}
		else if (mq > nq)
		{
			int d = C(np, mq), di = 1ll * d * mq % EI;
			posC = (posC - d + EI) % EI; posCi = (posCi - di + EI) % EI;
			if (mq & 1) d = EI - d, di = EI - di;
			negC = (negC - d + EI) % EI; negCi = (negCi - di + EI) % EI;
		}
		int resP = (1ll * nq * posC + (np ? 1ll * np * p2[np - 1] : 0)
			- posCi + EI) % EI,
			resN = (1ll * nq * negC + EI - (np == 1) - negCi + EI) % EI;
		resP = (1ll * resP - 1ll * nq * (p2[np] - posC + EI)
			% EI - posCi + EI + EI) % EI;
		resN = (1ll * resN - 1ll * nq * ((!np) - negC + EI)
			% EI - negCi + EI + EI) % EI;
		int res = nq & 1 ? 1ll * (resP - resN + EI) * I2 % EI
			: 1ll * (resP + resN) * I2 % EI;
		printf("%d\n", 1ll * i2[cnt] * res % EI);
	}
	return 0;
}