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\) 的数量:
- \(\forall i\in[1,n],\) 若 \(a_i>0\),则 \(p_i=a_i\);
- \(\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\):
- \(d(s,t)=d(s,c)+d(c,t)\);
- \(\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\),条件如下:
- \(x='P',y=z='R'\);
- \(x='R',y=z='S'\);
- \(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;
}