ARC132 总结

ARC132 总结

P.S. 由于笔者学艺不精,故若有理论错误或笔误之处敬请指出,求轻喷 😄

A - Permutation Grid

题意

给两个长为 \(n\) 的排列 \(R\)\(C\),要求用这两个排列构造一个 \(n\times n\)\(01\) 矩阵 \(A\),其中:

\(i\) 行恰有 \(R_i\)\(1\),第 \(j\) 列恰有 \(C_j\)\(1\)\(Q\) 次询问,每次会询问矩阵中一个位置的值。

做法

注意到 \(R\)\(C\) 都是排列,故 \(R\) 中至少一个位置值为 \(n\)\(C\) 中至少一个位置值为 \(1\)

\(C_i=1,R_j=n\),则第 \(i\) 列中所有位置,除了 \(A_{i,j}=1\),其他的都是 \(0\)

那么,我们再找到 \(R\) 中值为 \(n-1\) 的位置和 \(C\) 中值为 \(2\) 的位置,我们又可以确定完整一列的值。

我们再继续找 \(3,n-2;4,n-3;\cdots\) 如此下去,我们就可以唯一确定矩阵的所有元素的值。

当然,我们肯定不能把矩阵里的所有值暴力存下来,我们可以试着观察构造矩阵的过程,就有:

\(A_{i,j}=1\) 当且仅当 \(R_i+C_j>n\),那么我们在询问时判一下即可。

#include <bits/stdc++.h>

#define fi first
#define se second
#define mp make_pair
#define pb push_back
#define LL long long
#define pii pair < int , int >
#define ckmax(a, b) ((a) = max((a), (b)))
#define ckmin(a, b) ((a) = min((a), (b)))
//#define swap(u, v) u ^= v, v ^= u, u ^= v
#define rep(i, a, b) for (int i = (a); i <= (b); i++)
#define per(i, a, b) for (int i = (a); i >= (b); i--)
#define edg(i, v, u) for (int i = head[u], v = e[i].to; i; i = e[i].nxt, v = e[i].to)

using namespace std;

inline int read() {
	int x = 0, f = 1; char ch = getchar();
	while (ch < '0' || ch > '9') f = ch == '-' ? -1 : 1, ch = getchar();
	while (ch >= '0' && ch <= '9') x = x * 10 + ch - 48, ch = getchar();
	return x * f;
}

const int N (1e5 + 10);

int n, q;
int r[N];
int c[N];

int main() {
	n = read();
	rep (i, 1, n) r[i] = read();
	rep (j, 1, n) c[j] = read();
	q = read();
	string ans;
	while (q--) {
		int x = read(), y = read();
		if (r[x] + c[y] > n) ans.pb ('#');
		else ans.pb ('.');
	}
	cout << ans;
	return 0;
}

B - Shift and Reverse

题意

给长为 \(n\) 的排列 \(p\),可以进行两种操作,分别是翻转排列和将 \(p_1\) 放到排列末尾,

问将排列排好序的最小操作数,或输出无法做到排好序。

做法

首先,有解当且仅当排列长成一下两种样子:

\(i,i+1,i+2,\cdots,n,1,2,\cdots,i-1\)\(i,i-1,i-2,\cdots,1,n,n-1,n-2,\cdots,1\)

原因是,这样两种排列必然有解,因为 \(1,2,\cdots,n\) 也是这样的排列,

而显然任意两种这样的排列都可以通过若干次操作而互相转换。

若不是这种类型的排列,则排列中必存在 \(1\le i,j,k \le n\)

满足 \(p_i<p_k<p_j\)\(p_i>p_k>p_j\),那最终 \(p_k\) 必须在 \(p_i\)\(p_j\) 中间,

而显然我们无法通过题中的两种操作做到这一点。

那么,对于上面两种有解的排列类型,只需大力讨论一下即可,比较简单就不赘述了。

code

#include <bits/stdc++.h>

#define fi first
#define se second
#define mp make_pair
#define pb push_back
#define LL long long
#define pii pair < int , int >
#define ckmax(a, b) ((a) = max((a), (b)))
#define ckmin(a, b) ((a) = min((a), (b)))
//#define swap(u, v) u ^= v, v ^= u, u ^= v
#define rep(i, a, b) for (int i = (a); i <= (b); i++)
#define per(i, a, b) for (int i = (a); i >= (b); i--)
#define edg(i, v, u) for (int i = head[u], v = e[i].to; i; i = e[i].nxt, v = e[i].to)

using namespace std;

inline int read() {
	int x = 0, f = 1; char ch = getchar();
	while (ch < '0' || ch > '9') f = ch == '-' ? -1 : 1, ch = getchar();
	while (ch >= '0' && ch <= '9') x = x * 10 + ch - 48, ch = getchar();
	return x * f;
}

const int N (1e5 + 10);

int n;
int p[N];

int main() {
	n = read();
	int rt = 0;
	rep (i, 1, n) p[i] = read();
	rep (i, 1, n) if (p[i] == n) rt = i;
	int k = 0;
	int ok = 0;
	rep (i, 1, n) if (p[i] != i) ok = 1;
	if (!ok) return cout << 0, 0;
	if (rt == n) {
		if (p[rt - 1] == n - 1) k = 1;
	} else if (p[rt + 1] == 1) k = 1;
	int ans = 2e9;
//	cout << k << endl;
	if (k) {
		ckmin (ans, n - p[1] + 1);
		ckmin (ans, 1 + p[n] + 1);
	} else {
		ckmin (ans, 1 + p[1]);
		ckmin (ans, 1 + (n - p[n] + 1));
	}
	cout << ans;
	return 0;
}

C - Almost Sorted

题意

给值域为 \([1,n]\cup\left\{-1\right\}(n\le 500)\) 的序列 \(a\) 和整数 \(d(d\le 5)\)

求满足一下条件的排列 \(p\) 的数量:

  1. \(\forall i\in[1,n],\)\(a_i>0\),则 \(p_i=a_i\)
  2. \(\forall i\in[1,n],|p_i-i|\le d\)

做法

注意到 \(d\) 很小,且对某个 \(a_i=-1\) 的位置 \(i\)\(p_i\) 的取值范围仅在 \([i-d,i+d]\) 中,

故考虑状压DP,即 \(f_{i,S}\) 为填了 \(p\) 的前 \(i\) 位,\([i-d,i+d]\) 中数的使用情况为 \(S\) 的方案数。

转移是简单的,只需要注意那些已经在 \(a\) 中出现过的正数,并确保 \(S\) 中这些数都使用过即可。

个人认为这道题是本场比赛中最无脑的?

code

#include <bits/stdc++.h>

#define fi first
#define se second
#define mp make_pair
#define pb push_back
#define int long long
#define pii pair < int , int >
#define ckmax(a, b) ((a) = max((a), (b)))
#define ckmin(a, b) ((a) = min((a), (b)))
//#define swap(u, v) u ^= v, v ^= u, u ^= v
#define rep(i, a, b) for (int i = (a); i <= (b); i++)
#define per(i, a, b) for (int i = (a); i >= (b); i--)
#define edg(i, v, u) for (int i = head[u], v = e[i].to; i; i = e[i].nxt, v = e[i].to)

using namespace std;

inline int read() {
	int x = 0, f = 1; char ch = getchar();
	while (ch < '0' || ch > '9') f = ch == '-' ? -1 : 1, ch = getchar();
	while (ch >= '0' && ch <= '9') x = x * 10 + ch - 48, ch = getchar();
	return x * f;
}

const int N (505);
const int B (1 << 12);
const int mod (998244353);

int n, d;
int a[N];
int buk[N * 2];
int f[N][B + 5];

signed main() {
	n = read(), d = read();
	rep (i, 1, n) {
		a[i] = read();
		if (a[i] != -1) buk[a[i]] = 1;
	}
	rep (j, n + 1, n * 2) buk[j] = 1;
	int m = 2 * d + 1, E = (1 << m) - 1;
	rep (i, 1, n - 1) rep (S, 0, E) {
		int p = i - d, ok = 0;
		for (int j = 0; j < m; j++, p++) if (!(S & (1 << j))) {
			if (p <= 0) ok = 1;
			if (buk[p]) ok = 1;
		}
		if (ok) continue;
		if (i == 1) {
			int cc = 0;
			int q = i - d;
			for (int j = 0; j < m; j++, q++) if (S & (1 << j)) {
				if (q <= 0) continue;
				if (buk[q]) continue;
				cc++;
			}
			if (a[i] == -1 && cc != 1) continue;
			if (a[i] != -1 && cc != 0) continue;
			f[i][S] = 1;
		}
		int MSK = S >> 1;
		MSK |= (buk[i + d + 1]) << (m - 1);
		if (a[i + 1] != -1) {
			f[i + 1][MSK] = f[i][S];
			continue;
		}
		rep (j, 0, m - 1) if (!(MSK & (1 << j))) {
			int NS = MSK | (1 << j);
			f[i + 1][NS] = (f[i + 1][NS] + f[i][S]) % mod;
		}
	}
	int ans = 0;
	rep (S, 0, E) {
		int p = n - d, ok = 0;
		for (int j = 0; j < m; j++, p++) if (!(S & (1 << j))) {
			if (p <= 0) ok = 1;
			if (buk[p]) ok = 1;
		}
		if (ok) continue;
		ans = (ans + f[n][S]) % mod;
	}
	cout << ans;
	return 0;
}

D - Between Two Binary Strings

题意

定义 \(S_{n,m}\) 为所有包含 \(n\)\(0\)\(m\)\(1\)\(01\) 串所在的集合,

再定义 \(d(s_1,s_2)\) 为每次可以交换相邻字符,将 \(s_1\) 变成 \(s_2\) 所花的最少交换次数,

给两个 \(01\)\(s,t\in S_{n,m}\),求满足以下条件的 \(01\)\(c\)

  1. \(d(s,t)=d(s,c)+d(c,t)\)
  2. \(\sum\limits_{i=1}^{len(c)-1}[c_i=c_{i+1}]\) 尽可能大。

做法

首先的想法应该是考虑重新刻画答案满足的两个条件。

我们考虑对 \(s_1,s_2\in S_{n,m}\)\(d(s_1,s_2)\) 的值等于什么。

\(p(s,i)\) 代表 \(01\)\(s\) 中第 \(i\)\(1\)\(s\) 中的下标,则有结论如下:

\(d(s_1,s_2)=\sum\limits_{i=1}^m|p(s_1,i)-p(s_2,i)|\),以下是证明:

首先,为了将 \(s_1\) 变成 \(s_2\),我们肯定需要将 \(s_1\)\(s_2\) 中的每个字符对应起来,

代表 \(s_1\) 中的每个字符在变换成 \(s_2\) 后的位置。

那么,如果我们将 \(s_1\)\(s_2\) 排成两行,并将两行中的每个字符都想象成一个点,

我们对每个 \(i\),都从第一行的 \(i\) 位置,到第二行的 \(r_i\) 位置连一条直线,

那么所有直线的交点数就是答案的操作次数。

我们进一步研究,如何调整这些直线会让答案更优。

我们发现,如果在 \(s_1\) 中存在两个 \(1\) 字符,原位置分别是 \(i\)\(j\),那交换后位置就是 \(r_i,r_j\)

则当 \(i<j,r_i>r_j\) 时,我们交换 \(r_i,r_j\) 的值一定不劣。从直线交点的角度考虑,就是:

当在 \(s_1\) 中位置 \(i,j\) 的字符都是 \(1\),且分别从 \(i\)\(j\) 出发的两条直线有交点时,

交换从 \(i\)\(j\) 出发的两条直线的终点一定让直线交点数变少。

从这个角度证明是简单的,因为:若有另外一条直线在交换终点前与两条直线都无交点,

那么这条直线在交换终点后也必然与两条直线不交,而交换终点后,两条直线本身也不交了,

所以答案至少会减少 \(1\)

也就是说,我们证明了 \(r_{p(s_1,i)}=r_{p(s_2,i)}\)

\(s_1\) 中的第 \(i\)\(1\) 字符必然与 \(s_2\) 中的第 \(i\)\(1\) 字符对应。

那么我们再考虑此时答案的下界,我们发现,此时答案下界就是 \(\sum\limits_{i=1}^m|p(s_1,i)-p(s_2,i)|\)

原因是对于 \(s_1\) 中第 \(i\)\(1\) 字符 \(c\)\(s_1\) 中另外一个字符 \(x\)

如果在将 \(s_1\) 变成 \(s_2\)\(x\)\(c\) 的某一边,将 \(s_1\) 变成 \(s_2\)\(x\)\(c\) 的另一边,

那么无论如何 \(x\) 都会与 \(c\) 做一次交换,而因为我们证明了所有从 \(1\) 字符出发的直线两两无交,

故这个式子是不会算重的。

而这个答案的下界是容易达到的,只需要一些简单的构造即可得到。

那么,我们就可以轻松的刻画第一个条件了,即:

\(p_i,q_i,o_i\) 分别为串 \(s,t,c\) 中第 \(i\)\(1\) 的位置,则我们有

\(d(s,t)=d(s,c)+d(c,t) \Leftrightarrow \sum\limits_{i=1}^m|p_i-q_i|=\sum\limits_{i=1}^m|p_i-o_i|+\sum\limits_{i=1}^m|o_i-q_i|\)

我们发现,\(\forall i\in[1,n],|p_i-q_i|\le |p_i-o_i|+|o_i-q_i|\)

而式子当且仅当 \(o_i\in[\min(p_i,q_i),\max(p_i,q_i)]\) 时取等,

\(d(s,t)=d(s,c)+d(c,t)\) 等价于以下条件成立:

\(\forall i\in[1,n],o_i\in[\min(p_i,q_i),\max(p_i,q_i)]\)

我们再考虑题面中的第 \(2\) 个条件,容易发现,

\((\sum\limits_{i=1}^{len(c)-1}[c_i=c_{i+1}])=len(c)-1-\sum\limits_{i=1}^{len(c)-1}[c_i\neq c_{i+1}]\)

而后者等于串 \(c\) 的长度减去串中的连续段个数,

故想要最大化 \(\sum\limits_{i=1}^{len(c)-1}[c_i=c_{i+1}]\),其实等价于最小化 \(c\) 中的连续段个数。

那么,此时的问题就转化为了:给定两个长 \(m\) 的数组 \(l_i=\min(p_i,q_i),r_i=\max(p_i,q_i)\)

需要求一个字典序最小的串 \(c\),满足:\(c\) 的第 \(i\)\(1\) 字符的下标在 \([l_i,r_i]\) 内。

这个问题就可以用贪心或DP解决了,下面讲讲贪心的做法。

设当前考虑到第 \(i\) 位,如果 \(i>1\),则我们肯定会尽量让 \(c_i=c_{i-1}\) 成立,

具体的证明可以讨论 \(c_{i-1}\)\(0\) 还是 \(1\),再根据对 \(c\)\(1\) 位置的限制讨论此时 \(c_i\) 填什么,

再分别对于每一种情况下证明:

若此时 \(c_i\neq c_{i-1}\),则我们一定可以找到一种调整方案,使得答案不会更劣。

而当 \(i=1\) 时,我们只需枚举 \(c_1\)\(0\) 还是 \(1\),再分别做一次上面讲的贪心即可。

时空复杂度显然都是线性的。

code

#include <bits/stdc++.h>

#define fi first
#define se second
#define mp make_pair
#define pb push_back
#define LL long long
#define pii pair < int , int >
#define ckmax(a, b) ((a) = max((a), (b)))
#define ckmin(a, b) ((a) = min((a), (b)))
//#define swap(u, v) u ^= v, v ^= u, u ^= v
#define rep(i, a, b) for (int i = (a); i <= (b); i++)
#define per(i, a, b) for (int i = (a); i >= (b); i--)
#define edg(i, v, u) for (int i = head[u], v = e[i].to; i; i = e[i].nxt, v = e[i].to)

using namespace std;

inline int read() {
	int x = 0, f = 1; char ch = getchar();
	while (ch < '0' || ch > '9') f = ch == '-' ? -1 : 1, ch = getchar();
	while (ch >= '0' && ch <= '9') x = x * 10 + ch - 48, ch = getchar();
	return x * f;
}

const int N (3e5 + 10);

int n, m;
int l[N];
int r[N];
int p[N];
int q[N];
char s1[N];
char s2[N];

int main() {
	int len = (n = read()) + (m = read());
	scanf ("%s%s", s1 + 1, s2 + 1);
	int tot = 0;
	rep (i, 1, len) if (s1[i] == '1') p[++tot] = i;
	tot = 0;
	rep (i, 1, len) if (s2[i] == '1') q[++tot] = i;
	rep (i, 1, m) l[i] = min (p[i], q[i]), r[i] = max (p[i], q[i]);
	int rs = 0;
	rep (kase, 0, 1) {
		string ans;
		if (kase && (l[1] > 1)) continue;
		if (!kase && (r[1] == 1)) continue;
		ans.pb ('0' + kase);
		int cur = kase + 1;
		rep (i, 2, n + m) {
			if (ans.back() == '1') {
				if (l[cur] <= i && r[cur] >= i) cur++, ans.pb ('1');
				else ans.pb ('0');
			} else {
				if (r[cur] == i) cur++, ans.pb ('1');
				else ans.pb ('0');
			}
		}
		int cnt = 0;
		rep (i, 0, n + m - 1) if (ans[i] == ans[i + 1]) cnt++;
		ckmax (rs, cnt);
	}
	cout << rs;
	return 0;
}

E.(待补)

F - Takahashi The Strongest

题意

给整数 \(k,n,m(k\leq 12,1\leq n,m\leq 3^k)\)

以及 \(n+m\) 个字符集为 \(\left\{ 'P','R','S'\right\}\),长都为 \(k\) 的字符串,分别是 \(a[1\cdots n]\)\(b[1\cdots m]\)

同时,定义一个函数 \(f(s,i,j)\),其中 \(s\) 为字符集同为 \(\left\{ 'P','R','S'\right\}\),长同为 \(k\) 的字符串,

函数值为 \(f(s,i,j)=\sum\limits_{p=1}^kg(s_p,a_{i,p},b_{j,p})\),其中 \(g(x,y,z)\) 的定义为:

\(g(x,y,z)=1\) 当且仅当以下三个条件成立,否则 \(g(x,y,z)=0\),条件如下:

  1. \(x='P',y=z='R'\)
  2. \(x='R',y=z='S'\);
  3. \(x='S',y=z='P'\)

再定义 \(h(s)=\sum\limits_{i=1}^n\sum\limits_{j=1}^m[f(s,i,j)>0]\)

需要对于所有字符集同为 \(\left\{ 'P','R','S'\right\}\),长同为 \(k\) 的字符串 \(s\),求出 \(h(s)\) 的值。

做法

为了方便下面表述,在此先说明一些定义,需要将定义看清楚再继续往下看。

如果一个串的字符集为 \(\left\{ 'P','R','S'\right\}\) 且长度为 \(k\),则我们称这个串为一个策略串。

对于策略串 \(s\),我们用 \(s[l,r]\) 代表串 \(s\) 中第 \(l\) 个到第 \(r\) 个字符,这 \(r-l+1\) 个字符所连接成的字串。

我们称字母三元组 \((x,y,z)\) 是合法的,当且仅当 \(g(x,y,z)=1\)

我们称策略串三元组 \((p,q,r)\) 是合法的,当且仅当 \(\exists i\in[1,k]\) 使得 \((p_i,q_i,r_i)\) 合法。

对于策略串三元组 \((p,q,r)\),我们称后缀 \([l,k]\) 合法,当且仅当 \(\exists i\in[l,k]\) 使 \((p_i,q_i,r_i)\) 合法。

还有,对于策略串 \(s\),我们称数对 \((i,j)\) 合法,当且仅当 \((s,a_i,b_j)\) 合法。

最后,对于策略串 \(s\) 的一段后缀 \([l,k]\),我们称数对三元组 \((l,i,j)\) 合法,

当且仅当 \(\exists t\in[l,k]\),使得 \((s_t,a_t,b_t)\) 合法。

当然,若我们称某物 \(X\) 不合法,当且仅当,其不满足在 \(X\) 所在定义域下对"合法"的要求。

题意其实就是要对每个策略串 \(s\),求合法的 \((i,j)\) 对数,那么我们考虑计算不合法的 \((i,j)\) 对数,

最后再从所有的 \((i,j)\) 对数,即 \(n\times m\) 中减掉不合法对数就是这个串的答案。

故考虑计算不合法对数,我们可以考虑按字符串的每一位容斥,具体来说就是:

对于串 \(s\),所求的不合法的 \((i,j)\) 对数,即 \(n\times m-h(s)\) 的值,等于:

不合法的 \((2,i,j)\) 的对数,减去满足 \((s_1,a_{i,1},b_{j,1})\) 合法的不合法的 \((2,i,j)\) 的对数。

看着这个很绕,我们来捋一捋上面结论成立的原因,即:

数对 \((i,j)\) 不合法实际上等价于 \(\sum\limits_{p=1}^kg(s_p,a_{i,p},b_{j,p})=0\)

也等价于 \(g(s_1,a_{i,1},b_{j,1})+\sum\limits_{p=2}^kg(s_p,a_{i,p},b_{j,p})=0\)

也等价于 \([g(s_1,a_{i,1},b_{j,1})=1]+ [(\sum\limits_{p=2}^kg(s_p,a_{i,p},b_{j,p}))>0]=0\)

\(g(s_1,a_{i,1},b_{j,1})=1\) 等价于 \((s_1,a_{i,1},b_{j,1})\) 合法,

故当 \((s_1,a_{i,1},b_{j,1})\) 合法时数对 \((i,j)\) 一定不合法,

而对于 \(s[2,k]\) 这段后缀, 当 \((2,i,j)\) 合法时,数对 \((i,j)\) 一定也不合法,

即不合法的数对 \((i,j)\) 数量,

等于所有满足字符三元组 \((s_1,a_{i,1},b_{j,1})\) 不合法,且 \((2,i,j)\) 不合法的 \((i,j)\) 数量。

那么,用等式来表述,

我们设数 \(A\) 的值代表所有满足 \((2,i,j)\) 不合法的 \((i,j)\) 数量(此时 \((s_1,a_{i,1},b_{j,1})\) 合不合法都行),

设数 \(B\) 的值代表所有满足 \((2,i,j)\) 不合法,且 \((s_1,a_{i,1},b_{j,1})\) 不合法的 \((i,j)\) 数量,

则我们有 \(n\times m-h(s)=A-B\)

但怎么求 \(A\)\(B\) 呢?实际上,对于当前正在考虑的策略串 \(s\),我们需要求的 \(A\)

就等价于将所有 \(a,b\) 串的第一位,以及 \(s\) 的第一位删除后的一个子问题,

而我们所求的 \(B\),则等价于将所有 \(a,b\) 串中不满足特殊条件的串删除后,

再将剩余串和串 \(s\) 的第一位删除后的子问题。其中,对于策略串 \(t\in\left\{ a[1\cdots n],b[1\cdots m] \right\}\)

\(t\) 不满足特殊条件,当且仅当三元组 \((s_1,t_1,t_1)\) 不合法。

那么,我们现在掌握了计算单个 \(h(s)\) 值的方法,那暴力的方法就是但对于每一个 \(s\) 都这样算一遍,

但这样的时间复杂度显然会超时,那么我们考虑优化。

我们仔细研究上述的暴力方法,无非就是对于每个策略串 \(s\) 都求一遍 \(A\)\(B\) 的值,

而我们发现,我们可以一起计算所有的 \(h(s)\) 的值,具体来说,就是:

我们令函数 \(solve(len,a'[1\cdots n'],b'[1\cdots m'])\),其值为数组 \(r[0\cdots3^{len-1}-1]\)

其中 \(len\) 代表考虑策略串的后 \(len\) 位,

\(a'\) 数组代表经过前 面若干轮考虑,所有前面位满足特殊条件而保留的 \(a\) 中的串,

\(b'\) 数组和 \(a'\) 数组含义类似,只不过是基于 \(b\) 的。

\(r\) 数组的第 \(i\) 位代表值 \(h(str)\)

其中 \(str\) 是长为 \(len\),字符集为 \(\left\{ 'P','R','S'\right\}\) 的,字典序第 \(i\) 小的串。

这里写的有些不严谨,但可以将 \(str\) 理解为一个长为 \(len\) 的策略串,

虽然策略串的定义要求了长为 \(k\),而 \(h(s)\) 的定义域是所有策略串。

那么,我们求解 \(solve(len,a',b')=r\),只需要再多递归调用四次 \(solve\) 函数即可,具体如下:

我们先令 \(a''\) 字符串组,为将 \(a'\) 中所有串的第一位去掉后的新字符串组,

再令 \(b''\) 字符串组,含义和上面类似,我们调用 \(solve(len-1,a'',b'')=r'\)

(注意当 \(len=0\)\(r'[0]\) 值为 \(a''\) 长度乘 \(b''\) 长度,因为此时可以随便从 \(a'',b''\) 中各选一个)

我们再将 \(a'\)\(b'\) 中第一位不为 \('P'\) 的串整体删除,再将其余串的第一位删除,

我们此时得到了两个数组,记为 \(a^{'P'},b^{'P'}\)

我们再将上面操作中的字符 \('P'\) 替换成 \('R'\)\('S'\) 后分别再做一次上面操作,

记得到的数组分别为 \(a^{'R'},b^{'R'}\) 以及 \(a^{'S'},b^{'S'}\)。此时,我们分别调用三次 \(solve\) 函数,分别为:

\(solve(len-1,a^{'P'},b^{'P'})=r^{'P'}\)\(solve(len-1,a^{'R'},b^{'R'})=r^{'R'}\)

以及 \(solve(len-1,a^{'S'},b^{'S'})=r^{'S'}\)

那么我们再对每个属于策略串字符集的字符 \(ch\)

我们先找到唯一的字符 \(ch'\) 满足 \((ch,ch',ch')\) 合法,

再对 \(r'\) 中所有第一位为 \(ch\) 的串 \(c\)\(r'\) 中对应的下标 \(id\)

以及串 \(c\) 去除第一位后在 \(r^{ch'}\) 中的下标 \(id'\),我们都将 \(r'[id]\) 减去 \(r^{ch'}[id’]\) 后,

此时的 \(r'\) 数组即为我们要求的 \(solve(len,a',b')\) 的答案。

这样的复杂度可以用递归式证明是 \(O(k4^k)\) 的,已经足以通过本题。

实现的时候可以将 \(solve\) 函数中的 \(a'\)\(b'\) 也改成桶,以方便删第一位。

code

#include <bits/stdc++.h>

#define fi first
#define se second
#define mp make_pair
#define pb push_back
#define LL long long
#define pii pair < int , int >
#define ckmax(a, b) ((a) = max((a), (b)))
#define ckmin(a, b) ((a) = min((a), (b)))
//#define swap(u, v) u ^= v, v ^= u, u ^= v
#define rep(i, a, b) for (int i = (a); i <= (b); i++)
#define per(i, a, b) for (int i = (a); i >= (b); i--)
#define edg(i, v, u) for (int i = head[u], v = e[i].to; i; i = e[i].nxt, v = e[i].to)

using namespace std;

inline int read() {
	int x = 0, f = 1; char ch = getchar();
	while (ch < '0' || ch > '9') f = ch == '-' ? -1 : 1, ch = getchar();
	while (ch >= '0' && ch <= '9') x = x * 10 + ch - 48, ch = getchar();
	return x * f;
}

const int N (15);
const int C (200);
const int M (6e5);

int n, m, k;
LL a[M + 5];
LL b[M + 5];
LL rs[M + 5];
int id[C + 5];
int pw[N + 5];
LL nr[N][M + 5];
LL nf[N][M + 5];
LL ng[N][M + 5];

void solve (LL *f, LL *g, LL *r, int len) {
	if (!len) return r[0] = f[0] * g[0], void();
	int lim = pw[len - 1];
	rep (S, 0, lim - 1) nf[len][S] = f[S] + f[S + lim] + f[S + 2 * lim], 
						ng[len][S] = g[S] + g[S + lim] + g[S + 2 * lim];
	solve (nf[len], ng[len], nr[len], len - 1);
	rep (S, 0, lim - 1) r[S] = r[S + lim] = r[S + 2 * lim] = nr[len][S];
	rep (ch, 0, 2) {
		int nch = (ch + 1) % 3;
		rep (S, 0, lim - 1) nf[len][S] = f[S + nch * lim], 
							ng[len][S] = g[S + nch * lim];
		solve (nf[len], ng[len], nr[len], len - 1);
		rep (S, 0, lim - 1) r[S + ch * lim] -= nr[len][S];
	}
}

string ss;

int val (string str) {
	int res = 0;
	rep (i, 0, k - 1) res *= 3, res += id[str[i]];
	return res;
}

int main() {
	id['P'] = 0, id['R'] = 1, id['S'] = 2;
	k = read(), n = read(), m = read(), pw[0] = 1;
	rep (i, 1, k) pw[i] = pw[i - 1] * 3;
	rep (i, 1, n) cin >> ss, a[val (ss)]++;
	rep (i, 1, m) cin >> ss, b[val (ss)]++;
	solve (a, b, rs, k);
	rep (S, 0, pw[k] - 1) printf ("%lld\n", 1ll * n * m - rs[S]);
	return 0;
}
posted @ 2022-01-17 23:18  GaryH  阅读(91)  评论(0编辑  收藏  举报