题解:P12472 [集训队互测 2024] 基础 ABC 练习题

0.前言

省流:275307894a 对 蒟蒻们 使用了 模拟赛
蒟蒻们 rp++

1.思路

20pts(子任务1):

其实就是双倍经验,这里贴一下第一篇题解:

点击查看

第一眼看到本题显然会想到 dp,然后我们会想到类似分别记录当前 ABCABCABCABCBCACAB 的数量进行线性 dp 的思路,但是这样并不好去除重复方案,若去掉则时间复杂度过高。

于是,学到的一点是,在根据题意实在无法进行 dp 时,要尽量思考出一个满足题目要求的充要条件,并对该充要条件进行 dp。

我们发现题目中要求的 \(n\) 个子序列很有特点:ABCBCACAB,具体来说是关于 ABC 的三个轮换,这里是不是会存在一些巧妙的性质呢?答案是有的。

不妨记 \(x_A\) 表示 A 的个数,\(x_B\) 表示 B 的个数,\(x_C\) 表示 C 的个数。

具体来说,我们拿 A 举例,我们发现在 BCACAB 中,A 是一定会在 C 的后面的,即如果只有这两种子序列,对于任意前缀子串而言,\(x_A\) 一定不大于 \(x_C\),但事实上我们还存在子序列 ABC,在该子序列中,A 是会排在 C 之前的,故我们此时有结论:

  • 对于任意前缀子串而言,\(x_A-x_C\) 一定不大于 ABC 的个数。

类似的,我们可以写出轮换的另外两个结论。而由于子序列个数为 \(n\),于是此时我们得到了一个看上去并没有那么强但也还不弱的结论:

  • \(A'=\max(x_A-x_C)\)\(B'=\max(x_B-x_A)\)\(C'=\max(x_C-x_B)\),则有 \(A'+B'+C'\le n\)。其中 \(\max\) 表示前缀最大值。

到这里我们其实已经论证了该结论的必要性,即对于满足条件的方案,一定满足该结论。但是如果满足该结论,是否就一定说明当前方案满足条件呢?

如果还能论证该结论的充分性,我们就可以顺利地将原问题转化为新的充要问题进行 dp 了。

考虑构造方案:对于第 \(i\)A,我们找到第 \(i+B'\)B,由于对于任意前缀 B 的个数最多比 A\(B'\) 个,故第 \(i+B'\)B 一定在第 \(i\)A 之后;类似地,我们找到第 \(i+B'+C'\)C,以及第 \(i+B'+C'+A'\)A。如果下标超出 \(n\) 则对 \(n\) 取模。此时又由于 \(B'+C'+A'\le n\),所以第 \(i+B'+C'\)C 一定在第 \(i+B'+C'+A'\)A 前面,同样也一定在第 \(i\)A 前面。故此时对应的三个字母构成了一个前后顺序正确的环,且只会绕整个串至多一次,又由于此时这些字母一一对应,故充分性显然

于是我们便可以对转化后的问题设计 dp 了!由于我们需要记录的信息是「前缀最大」的 \(x_A-x_C\)\(x_B-x_A\)\(x_C-x_B\),所以我们还需要额外记录下当前 \(a,b,c\) 的个数,由于 \(a+b+c=i\) 的原因我们可以省去一维,故总状态数为 \(O(n^6)\),转移为 \(O(1)\),时间复杂度 \(O(n^6)\)

40pts(子任务1,2):

由于串固定,所以可以先枚举 ABC,BCA,CAB 的数量。
容易想到枚举每个 A,B,C 的位置
显然,第一个字符要作为一个字符串的第一个字符,最后一个字符要作为一个字符串的最后一个字符
故考虑贪心
ABC 的数量=\(A\)BCA 的数量=\(B\)CAB 的数量=\(C\),则方案如下

ABC BCA CAB
第一个字符所在位置 \((1,A)\) \((1,B)\) \((1,C)\)
第二个字符所在位置 \((B+1,B+A)\) \((C+1,C+B)\) \((A+1,A+C)\)
第三个字符所在位置 \((C+B+1,C+B+A)\) \((A+C+1,A+C+B)\) \((B+A+1,B+A+C)\)

这里的位置指的是相对位置。
形式化地,如设所有 A 中的第 \(i\)A\(a_i\),则 \((1,A)\)ABCA 应出现在 \((a_1,a_A)\) 范围内。
也即,当每个串最前面的字符出现在每种字符的最前面,最后的字符出现在每种字符的最后面时,若不存在方案,则说明 \(A,B,C\) 不是一个可行的方案。

省流:前面在前面,后面在后面,如果没方案,肯定没方案

具体证明:

点击查看

反证,假设存在一种方案在 \(A,B,C\) 的约束下成立,但在以上方案不成立,则可以通过数次交换 ABC,BCA,CAB 中的 A,B,C,使其最终化为此方案
我手搓的样例

ABCCAB

如选取 \(a_1,b_1,c_1\) 组成 ABC,\(c_2,a_2,b_2\) 组成 CAB
则显然 \(a_1,b_1,c_2\) 组成 ABC,\(c_1,a_2,b_2\) 组成 CAB 一定也是一个可行的方案。
故,在以上方案成立是当前方案成立的充分必要条件

省流:上一句话是对的

于是,你就得到了一种在 \(O(n^3)\) 时间内求出一个方案是否成立的方法。
可以由题目推出,\(∀n \le 60\)

67pts:

考虑给定\(A\), \(B\) 如何计数,则上面的最优分配方案可以转化为对于\(p_a,i\)\(p_b,B+i\) 等的顺序限制。
然后设 \(f_{a,b,c}\) 表示当前填了前 \(a\)A\(b\)B\(c\)C 的方案数。这个dp是 \(O(n^3)\) 的。
然后对于一般的情况,只需要枚举哪些子集对是合法的,容斥即可计算答案。
注意到如果两个子集 \(max\) 加起来大于 \(n\) 肯定是无解的,因此只有
\(O(2^nn)\) 个子集对需要计算,时间复杂度 \(O(2^nn^4)\),可以通过\(n≤15\)

100pts

尝试优化枚举集合的过程。
考虑 \(|S1| = |S2| = n + 1\) 的情况。

  • 假设现在 ABC 个数的子集已经确定,则前 \(i\)\(A\) 已经确定了。在考虑要将哪些 \(B\) 分配进去的时候,会对于 \(B\) 有一个限制,可以表示为 \(B\) 要大于某个值。
  • 对于剩下的分配,限制也总能表示成 \(B\) 要大于某个值
    或者 \(B\) 要小于某个值。换而言之,\(B\) 的取值是一个区间。

在得到这个结论以后,只需要判定 \(B\) 取值区间非空即可。
对于这样的判定,可以使用点减边容斥:枚举每个 \(i ∈ [0, n]\),计算 \(B\) = \(i\) 合法的方案数,然后对于每个 \(i ∈ [1, n]\),减去 \(B = i - 1, B = i\) 同时合法的方案数,即可得到 \(B\) 取值区间非空的方案数。
对于不满足特殊性质的情况,只需要将所有 \(S1\), \(S2\) 中的元素拿出来做这个容斥即可。
同理,对于 \(A\) 的枚举也可以应用这样的优化,因此总共只需要枚举 \(O(n^2)\) 个集合对,时间复杂度 \(O(n^5)\)

这样,你就吊打了绝大多数的集训队选手。
——275307894a

2.代码

点击查看代码
#include <bits/stdc++.h>
using namespace std;
const int N=65,MOD=1e9+7;
using ui=unsigned;
int n;
ui ans,dp[N][N][N][4];
char a[N*3],s1[N],s2[N];
int main() {
	int t,op;
	scanf("%d%d",&t,&op);
	while(t--) {
		scanf("%d %s%s%s",&n,s1,s2,a+1);
		if(n^60) {
			puts("-1");
			continue;
		}
		ans=0;
		for(int x=0; x<=n; ++x)for(int z=0; z<=n; ++z) {
				int Z=z;
				while(Z<=n&&s1[Z]=='0')Z++;
				int X=x;
				while(X<=n&&s2[X]=='0')X++;
				int y=n-X-Z;
				if(y<0)continue;
				dp[0][0][0][0]=1;
				for(int i=0,t; i<=n; ++i) {
					for(int j=0; j<=n && j<=i+x; ++j) {
						for(int k=max(i-z,0); k<=n && k<=j+y; ++k) {
							t=i+j+k;
							if(!t)continue;
							memset(dp[i][j][k],0,sizeof(dp[i][j][k]));
							if(i && j-i<x && (a[t]=='A' || a[t]=='?')) {
								dp[i][j][k][0]+=dp[i-1][j][k][0];
								dp[i][j][k][1]+=dp[i-1][j][k][1];
								dp[i][j][k][2]+=dp[i-1][j][k][2];
								dp[i][j][k][3]+=dp[i-1][j][k][3];
							}
							if(j && k-j<y && (a[t]=='B' || a[t]=='?')) {
								dp[i][j][k][0]+=dp[i][j-1][k][0];
								dp[i][j][k][1]+=dp[i][j-1][k][1];
								dp[i][j][k][2]+=dp[i][j-1][k][2];
								dp[i][j][k][3]+=dp[i][j-1][k][3];
							}
							if(k && i-k<z && (a[t]=='C' || a[t]=='?')) {
								dp[i][j][k][0]+=dp[i][j][k-1][0];
								dp[i][j][k][1]+=dp[i][j][k-1][1];
								dp[i][j][k][2]+=dp[i][j][k-1][2];
								dp[i][j][k][3]+=dp[i][j][k-1][3];
							}
							if(j-i==x) {
								dp[i][j][k][1]+=dp[i][j][k][0];
								dp[i][j][k][3]+=dp[i][j][k][2];
								dp[i][j][k][0]=dp[i][j][k][2]=0;
							}
							if(i-k==z) {
								dp[i][j][k][2]+=dp[i][j][k][0];
								dp[i][j][k][3]+=dp[i][j][k][1];
								dp[i][j][k][0]=dp[i][j][k][1]=0;
							}
							// cerr<<dp[i][j][k][0]<<' '<<dp[i][j][k][1]<<' '<<dp[i][j][k][2]<<' '<<dp[i][j][k][3]<<'\n';
						}
					}
				}
				ans+=dp[n][n][n][3];
			}
		printf("%u\n",ans);
	}
	return 0;
}

3.后记

  1. 膜拜巨佬 275307894a
  2. 双倍经验,而且呈包含关系
  3. 给好不容易搞懂此题的本蒟蒻一个赞吧
posted @ 2025-07-11 15:28  yzc_is_SadBee  阅读(13)  评论(0)    收藏  举报