• Codeforces Global Round 12 题解


    原题解

    A

    题意

    给定一个长度为 (n) 的字符串,重新排列字符,使得其不包含 trygub 作为子序列。

    (1le nle 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))

    求让所有点坐标相同的最少操作数或告知无解。

    (2le nle 100)(0le kle10^6)(0le x_i,y_ile 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 imes n) 的含 XO. 的棋盘,改动一些不为 . 的位置(不能改成 .),使得没有横向或纵向连续 (3) 个都为 XO

    如果不为 . 的位置有 (k) 个,那么你要给出一个改动不超过 (lfloorfrac k3 floor) 的方案。

    (1le nle 300),多测,数据组数不超过 (100)

    对于简单版,初始棋盘没有 O

    Solution

    考虑构造一个模板:((i,j))O 当且仅当 (i+jequiv x(mod 3))(xin{0,1,2}),显然这样是合法的。

    这样对于简单版就可以选择一个改动次数最少的 (x) 进行求答案了。

    对于困难版,考虑这个模板的扩展:如果原棋盘上 ((i,j))X,则 ((i,j))O 当且仅当 (i+jequiv x(mod 3)),否则 ((i,j))X 当且仅当 (i+jequiv x+1(mod 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),对于所有 (1le ile n),判断数列所有长度为 (i) 的区间最小值是否构成一个 (1)(n-i+1) 的排列。

    (1le nle 3 imes10^5)(1le a_ile n),多测,数据组数不超过 (10^4),所有数据的 (n) 之和不超过 (3 imes10^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)

    (1le nle 200)(n-1le mle 2000)

    Solution

    考虑把条件 (a_i-a_jin{-1,1}) 扩充成 (-1le a_i-a_jle 1),就转化成了差分约束问题,如果一条边被钦定为 (i)(j) 则直接 (1le a_j-a_ile 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
    %d
    ", 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),使得对于所有 (1le i<n) 都有 (a_{p_i} e a_{p_{i+1}}) 且最小化 (sum_{i=1}^{n-1}[|p_i-p_{i+1}|>1])

    (1le nle 10^5)(1le a_ile n),多测,数据组数不超过 (10^4),所有数据 (n) 之和不超过 (10^5)

    Solution

    问题转化成求一个最小的 (k),使得数列能被分成 (k) 段(相邻相等的数之间必须分段),这些段能够以任意顺序拼接起来(可翻转不分段),使得任意接口处的两个数不同。

    先考虑原问题有解的条件:出现最多的那种数出现次数 (c) 满足 (2cle n+1)

    把每段看成一个无序二元组 ((x,y)),要把 (k) 个二元组拼接起来,使得对于拼接处有 (y_i e x_{i+1}),我们有个类似的结论:

    有解当且仅当这 (2k) 个数中出现最多的那种数出现次数 (c) 满足 (cle k+1)

    证明可以归纳:

    如果 (cle k) 且存在一个二元组的 (x e y),则可以把这个二元组之外的 (k-1) 个二元组先接好之后再接上 ((x,y)),注意到这里 (x)(y) 一定有一端能接上。
    如果 (cle k) 且所有二元组都有 (x=y),则这就相当于把 (k) 个数拼接起来并且出现次数最多的那种数出现了不超过 (lfloorfrac k2 floor) 次,这时一定有解。
    如果 (c=k+1),设出现 (k+1) 次的数为 (u),则可以计算出如果有 (m)((u,u)) 就有 (m-1)(( e u, e u)),这时候可以让 ((u,u))(( e u, e u)) 交替排列,然后把所有的 (( e u,u)) 都接到后面,就达到了目的。

    回到问题,先把所有的相邻相同的数之间都分割一下,如果已经满足了 (cle k+1) 则已经得到答案。

    否则设出现 (k+1) 次的数为 (u),依次考虑每个 (( e u, e u)) 的相邻数对,在其中分割一下,直到 (cle 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
    ", 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
    ", ans);
    		}
    	puts("-1");
    }
    
    int main()
    {
    	int T; read(T);
    	while (T--) work();
    	return 0;
    }
    

    G

    题意

    给定一个长度为 (n),字符集为 (20) 的字符串,每次操作可以选择两个个字符 (x e y),满足 (x)(y) 都在现在的串中出现过,且如果字符 (x) 出现的位置从小到大依次为 (i_1,i_2,dots,i_m),则需要满足 (frac ab imes (i_m-i_1+1)le m),把所有的字符 (x) 改成 (y)

    求出所有的字符 (x),满足可以通过若干次操作使得所有的字符都变成 (c)

    (1le nle 5000)(1le ale ble 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 imes (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 imes (r-l+1)le m) 的字符。

    不难发现把所有的字符都变成 (c),相当于把除 (c) 以外的所有字符都合并成若干个满足 (frac ab imes (r-l+1)le m) 的字符之后再一一合并到 (c) 上。

    所以 (f_S=igcup_{cin 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) 的一个下标,然后需要再次回答。

    (2le nle2 imes10^5)(0le mle2 imes10^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_wge 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-nequiv0(mod 4)),显然满足 (odd_w+even_b=i) 的方案数是一个组合数,可以直接计算,这样可过简单版。

    对于复杂版,考虑这个组合数和式的通式:

    [sum_{i=0}^zinom zi|i-x|[imod 2=y] ]

    考虑大力分 (ile x)(i>x)(inom zi)(iinom zi) 的和分开维护,([imod 2=y]) 可用单位根反演等技巧搞掉。

    注意到一次修改只会让 (z)(x)(1) 或减 (1),对于 (z) 加一之后组合数前缀和的变化,我们根据组合数递推公式有:

    [sum_{i=0}^xinom {z+1}i=2sum_{i=0}^xinom zi-inom 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
    ", 1ll * i2[cnt] * res % EI);
    	}
    	return 0;
    }
    
  • 相关阅读:
    查看jvm的cg情况
    什么是json
    httpclient工具类
    mysql 优化思路(1)
    mysql存取日期出问题
    springboot和tomcat jar包冲突
    递归
    如何去理解return?
    js BOM判断当前窗口是否最顶层。
    个人笔记,关于ajax 如果没有请求成功不允许再次请求的方法。
  • 原文地址:https://www.cnblogs.com/xyz32768/p/14138096.html
Copyright © 2020-2023  润新知