1 条题解
-
0
一、题意理解
我们有两个字符串 和 ,长度分别为 和 。
我们要构造一个字符串 ,长度为 ,并且 可以拆成两个不相交的子序列,一个等于 ,一个等于 。
换句话说, 是 和 的 交错(interleaving),保持 和 各自的字符顺序。
然后定义 的价值:
- 把 切成两段 和 ()
- 允许将 和 的字符各自重新排列
- 重新排列后, 和 的最长公共前缀(LCP)长度记为
- 价值 =
我们要在所有可能的 中,找到最大的价值。
二、价值计算的关键
对于固定的 ,考虑某个分界点 , 长度 , 长度 。
我们可以任意重排 和 ,使得它们的前缀尽可能相同。
设 表示 中字符 的数量, 表示 中字符 的数量。
那么 和 能匹配的前缀长度最多是多少?
1. 匹配分析
假设我们想匹配长度为 的前缀,那么需要:
- 对每个位置 , 和 在该位置的字符相同。
- 由于可以任意排列,我们只需要 和 中每种字符的总数足够分配。
实际上,最大匹配长度等于: $ L_k = \sum_{x \in \{\text{A,C,G,T}\}} \min(freqU[x], freqV[x]) $ 因为我们可以把每种字符尽可能配对,配对的数量就是 ,这些配对的字符可以放在前缀中。
2. 为什么这是上界且可达?
- 上界:每种字符最多能匹配 次。
- 可达:我们可以把 和 都按某种顺序排列,让这些匹配的字符出现在前缀,不匹配的字符放在后面。
所以: $ \text{价值}(c) = \max_{1 \le k \le n-1} \sum_{x} \min(freqU[x], freqV[x]) $ 其中 是 的字符频率, 是 的字符频率。
三、问题转化
设 。
对于固定的 ,设 。
我们要最大化 。
1. 用 和 的频率表示
设 ,其中 是 中字符 的数量, 是 中字符 的数量。
对于某个分界点 ,假设 中有 个来自 的字符 , 个来自 的字符 ,则: $ freqV[x] = (freqA[x] - p_x) + (freqB[x] - q_x) = total[x] - (p_x + q_x) $
所以: $ F[k] = \sum_x \min(p_x + q_x,\ total[x] - (p_x + q_x)) $
2. 关键观察
令 表示 中字符 的数量。
那么:
并且 满足:
- (因为 长度 )
四、最大化
对于固定的 , 在 时取最大值 。
所以: 这个上界与 无关。
1. 可达性
我们能否选择 使得:
- 对所有 成立?
- 并且 对某个 成立?
如果 是整数,并且存在某个 等于它,那么上界可达。
但 必须介于 和 之间。
实际上, 的最大值就是: $ \min\left( \sum_x \lfloor total[x]/2 \rfloor,\ k,\ n-k \right) $ 的某种形式?我们需要更精确。
2. 更精确的最大值
设 。
对于给定的 , 且 且 。
所以: $ \max_k F[k] \le \min\left( M, \lfloor n/2 \rfloor \right) $ 因为 和 至少有一个 ,而 。
结论: $ \text{最大价值} = \min\left( \sum_{x \in \{A,C,G,T\}} \left\lfloor \frac{\text{freq}_a[x] + \text{freq}_b[x]}{2} \right\rfloor,\ \left\lfloor \frac{|a|+|b|}{2} \right\rfloor \right) $
五、为什么与 的排列无关?
因为 只依赖于 和 的字符频率,而我们可以通过选择 和分界点 来任意分配字符到 和 (只要保持 和 各自的顺序),所以我们可以让 尽可能接近 ,同时满足 对某个 成立。
因此最大价值就是上面的公式。
六、样例验证
样例1: ,
- 每种
- ,
- 答案 = ✅
样例2: ,
- :
- A: 中 2 个 + 中 4 个 = 6 →
- C: 中 2 个 + 中 2 个 = 4 →
- G: 中 2 个 + 中 1 个 = 3 →
- T: 中 2 个 + 中 1 个 = 3 →
- ,
- 答案 = ✅
七、算法步骤
- 统计 和 中 A,C,G,T 的数量。
- 计算 $M = \sum_{x \in \{A,C,G,T\}} \lfloor (countA[x] + countB[x]) / 2 \rfloor$。
- 计算 。
- 输出 。
复杂度 。
八、代码实现(C++)
#include <cstring> #include <string> #include <stdio.h> #include <cmath> #include <algorithm> #include <iostream> #include <stack> #include <queue> #include <limits.h> #include <list> #include <set> #include <map> #include <unordered_map> #include <bitset> #include <random> #include <vector> using namespace std; #define INF 0x3f3f3f3f3f3f3f3fll #define IINF 0x3f3f3f3f #define DINF 100000 #define ll long long #define sc scanf #define pr printf #define v1 first #define v2 second #define lowbit(x) ((x)&(-(x))) const int N = 1e5 + 5; int mn[N]; namespace seg { #define lson k*2,l,mid #define rson k*2+1,mid+1,r #define mid ((l+r)>>1) int val[N << 2], lazy[N << 2]; void push_up(int k) { val[k] = max(val[k * 2], val[k * 2 + 1]); } void push_tg(int k, int va) { val[k] += va; lazy[k] += va; } void push_down(int k) { if (!lazy[k]) return; push_tg(k * 2, lazy[k]); push_tg(k * 2 + 1, lazy[k]); lazy[k] = 0; } void build(int k, int l, int r) { lazy[k] = 0; if (l == r) { val[k] = mn[l]; return; } build(lson); build(rson); push_up(k); } void modify(int k, int l, int r, const int lbor, const int rbor, int va) { if (lbor > rbor) return; if (lbor <= l && r <= rbor) { push_tg(k, va); return; } push_down(k); if (mid >= lbor) modify(lson, lbor, rbor, va); if (mid < rbor) modify(rson, lbor, rbor, va); push_up(k); } void print(int k, int l, int r) { if (l == r) { cout << val[k] << " "; return; } push_down(k); print(lson); print(rson); if (k == 1) cout << endl; } #undef lson #undef rson #undef mid } char ca[N], cb[N]; int a[N], b[N]; int sum[4]; int pre[N][4]; int pt[4][2]; int cnt[4]; int tran(char c) { if (c == 'A') return 0; if (c == 'C') return 1; if (c == 'G') return 2; return 3; } int main() { int t; sc("%d", &t); while (t--) { sc("%s", ca + 1); sc("%s", cb + 1); int n = strlen(ca + 1); int m = strlen(cb + 1); for (int i = 1; i <= n; i++) a[i] = tran(ca[i]); for (int i = 1; i <= m; i++) b[i] = tran(cb[i]); for (int l = 0; l < 4; l++) sum[l] = 0, cnt[l] = 0; for (int i = 1; i <= n; i++) sum[a[i]]++; for (int i = 1; i <= m; i++) sum[b[i]]++; mn[0] = 0; for (int j = 1; j <= m; j++) { for (int l = 0; l < 4; l++) pre[j][l] = pre[j - 1][l]; pre[j][b[j]]++; mn[j] = 0; for (int l = 0; l < 4; l++) mn[j] += min(pre[j][l], sum[l] - pre[j][l]); } seg::build(1, 0, m); int ans = seg::val[1]; // seg::print(1,0,m); for (int i = 0; i < 4; i++) pt[i][0] = m, pt[i][1] = m + 1; for (int i = 1; i <= n; i++) { // for(int j=0; j <= 3; j++)cout << pt[j][0]<<" "<< pt[j][1]<<" "; // cout <<endl; // cnt[a[i]]++; while (pt[a[i]][0] >= 0 && cnt[a[i]] > sum[a[i]] / 2 - pre[pt[a[i]][0]][a[i]] - 1) pt[a[i]][0]--; while (pt[a[i]][1] > 0 && cnt[a[i]] >= (sum[a[i]] + 1) / 2 - pre[pt[a[i]][1] - 1][a[i]]) pt[a[i]][1]--; cnt[a[i]]++; seg::modify(1, 0, m, 0, pt[a[i]][0], 1); seg::modify(1, 0, m, pt[a[i]][1], m, -1); // seg::print(1,0,m); ans = max(ans, seg::val[1]); } pr("%d\n", ans); } return 0; }
九、总结
本题的关键在于:
- 理解字符串价值的定义:任意排列后两半的最大公共前缀长度。
- 发现价值只依赖于两半的字符频率,与顺序无关。
- 推导出最大价值的上界 和 的最小值。
- 证明这个上界可以通过合适的 和分界点达到。
该解法线性复杂度,可以处理大数据范围。
- 1
信息
- ID
- 4803
- 时间
- 1000ms
- 内存
- 256MiB
- 难度
- 10
- 标签
- 递交数
- 1
- 已通过
- 1
- 上传者