P11052 [IOI 2024] 象形文字序列
Sub4 保证了存在合法的子序列,这提示我们先找到一个备选的子序列,再去判断它是否合法。
考虑找几个显然的性质。如果我们只保留某一种字符 \(c\),那么只含 \(c\) 的最大长度就是 \(c\) 在 \(A\) 和 \(B\) 中出现次数的较小值,而这个需要是答案的子序列。
那么对于每种字符 \(c\),答案子序列 \(C\) 要么包含 \(A\) 中的所有 \(c\),要么包含 \(B\) 中的所有 \(c\)。对于 \(C\) 的每一位 \(C_i\),我们假设它分别匹配了 \(A_{pa_i}\) 和 \(B_{pb_i}\)。那么也可以看成我们想去找一个 \(A\) 与 \(B\) 之间的匹配,并且匹配不交。\(C\) 包含某个序列里的所有字符相当于钦定了这些位置在匹配中,而我们相当于去把这些位置归并。
考虑贪心。我们分别找 \(A\) 和 \(B\) 中第一个未匹配的钦定位,设为 \(A_i\) 和 \(B_j\)。如果一个位置 \(p\) 被匹配,那么这个序列的 \(1\sim p\) 就都可以删除不管,因此不妨认为当前两个序列都是还没开始匹配的状态。
先判掉一些平凡的情况:
- \(A_i=B_j\):这时只能匹配 \(A_i\) 和 \(B_j\)。
- \(A_{1\sim i}\neq B_j\):这时我们无法匹配 \(B_j\),只能匹配 \(A_i\)。
- \(A_i\neq B_{1\sim j}\):同上,只能匹配 \(B_j\)。
- \(B_{j\sim m}\) 中 \(A_i\) 个数严格小于 \(A_{i\sim n}\):如果不去匹配 \(A_i\),那么后面肯定就匹配不完 \(A\) 了。因此匹配 \(A_i\)。
- \(A_{i\sim n}\) 中 \(B_j\) 个数严格小于 \(B_{j\sim m}\):同上,只能匹配 \(B_j\)。
如果上面的情况无法找到不交的匹配,那么直接无解。
现在我们还有一个不好做的情况:我们无法通过当前状态推出现在该匹配哪个数。
因为我们全程只和 \(A_i\) 和 \(B_j\) 有关,不妨设 \(A_i=0,B_j=1\),然后把序列写成 \(01\) 序列。序列中 \(\neq A_i,B_j\) 的数是不重要的,可以直接忽略。因为我们取出的是第一个未匹配数,因此有 \(A_{1\sim i-1}=1,B_{1\sim j-1}=0\)。
不妨设:
- \(A_{1\sim i-1}\) 有 \(a\) 个 \(1\)。
- \(A_{i+1\sim n}\) 有 \(b\) 个 \(0\)。
- \(B_{1\sim j-1}\) 有 \(c\) 个 \(0\)。
- \(B_{j+1\sim m}\) 有 \(d\) 个 \(1\)。
由 \(2,3\),我们有限制 \(a>0,c>0\)。
那么我们可以构造出两个公共子序列:
- \(a\) 个 \(1\),然后 \(b+1\) 个 \(0\)。
- \(c\) 个 \(0\),然后 \(d+1\) 个 \(1\)。
这两个子序列都要是 \(C\) 的子序列,因此 \(C\) 要么包含 \(a+d+1\) 个 \(1\),要么包含 \(b+c+1\) 个 \(0\)。但是 \(C\) 又只能有 \(b+1\) 个 \(0\) 和 \(d+1\) 个 \(1\),所以这些都是不合法的。
于是我们有结论:在判掉上面 \(5\) 种情况后,其它情况都是无解!
那么我们只要去维护上面的 \(5\) 种情况,开几个桶扫一下就好,不难做到 \(O(n+m)\) 通过 Sub4。
现在我们要去判定找出的 \(C\) 是否合法。回顾之前的贪心,我们求出的 \(C\) 保证了是 \(A\) 和 \(B\) 的子序列,并且每种字符的出现次数是对的。那么我们想找的是一个公共子序列 \(D\),且不是 \(C\) 的子序列。
不妨设 \(C\) 的长度为 \(t\)。对于 \(C_i\),它在 \(A\) 和 \(B\) 中的匹配的位置是 \(pa_i\) 和 \(pb_i\)。
考虑一个判定方式:我们顺序枚举 \(D\) 的每个元素,同时维护当前和三个序列分别匹配到 \(C_p,A_i,B_j\)。但是直接做有三维,把一维放到 dp 的值还有两维,不好维护,考虑继续分析性质。
如果 \(i\ge pa_p\) 且 \(j\ge pb_p\),那么我们把当前 \(D\) 的前缀换成 \(C_{1\sim p}\) 能使 \(i=pa_p,j=pb_p\),是不劣的。
如果 \(i<pa_p\) 且 \(j<pb_p\),那么这时我们把后面的 \(D\) 接上 \(C_{p\sim t}\) 是合法的,而这样 \(D\) 就不是 \(C\) 的子序列,可以导出 \(C\) 不合法。
那么我们只剩下了两种情况:
- \(i\ge pa_p\),\(j\le pb_p\)。
- \(i\le pa_p\),\(j\ge pb_p\)。
同时我们可以发现,因为当前 \(D\) 的前缀是 \(C_{1\sim p}\) 的子序列,因此如果 \(i\ge pa_p\),那么我们让 \(i=pa_p\) 是可行的。于是两类就变成:
- \(i=pa_p\),\(j\le pb_p\)。
- \(i\le pa_p\),\(j=pb_p\)。
这样我们就只有两维了,对于第一类,我们可以把 \(j\) 放到 dp 的值,第二类同理,这样 dp 的空间就是可接受的了。
转移似乎并不简单,因为两类之间相互转移不好处理。不妨分析一下第一类变成第二类的情况。
不妨设我们从 \(C_p\) 转移到 \(C_q\),\(p\) 处匹配是 \((pa_p,j)\),\(q\) 处是 \((i',pb_q)\)。
但是我们可以发现,我们可以认为 \(p\) 处匹配是 \((pa_p,pb_p)\)。因为两维是独立的,这样实际上也能转移到 \((i',pb_q)\),而前者也是属于第二类的。
那么我们通过调整证明了实际上不会有两类之间的转移,于是可以分开判定。不妨考虑第一类。
\(dp_p\) 表示我们匹配到了 \(C_p\),此时 \(i=pa_p\),\(j\) 的最小值。
不妨设我们想从 \(dp_q\) 转移到 \(dp_p\),需要满足的条件:
- 对于 \(A_{pa_q}\),往后加了一个 \(C_p\) 字符后可以匹配到 \(A_{pa_p}\),但是我们允许不匹配第一个位置。
- 对于 \(C_q\),往后加了一个 \(C_p\) 字符后匹配到 \(C_p\),这里必须是第一个位置。
设 \(lstA_i\) 和 \(nxtA_i\) 表示序列 \(A\) 中 \(A_i\) 的上一个、下一个 \(=A_i\) 的位置。\(B,C\) 同理。
可以发现条件 \(1\) 对我们的转移是没有约束的,条件 \(2\) 可以写成 \(q\ge lstC_p\)。
对于所有的合法的 \(q\),\(dp_p\) 只关心其中最小的 \(dp_q\),因此可以用线段树优化转移做到 \(O(n\log n)\)。
注意我们还要判断无解。无解有两种形式,第一种是在匹配过程中出现 \(i<pa_p\) 且 \(j<pb_p\),另一种是匹配到最后 \((p,dp_p)\),我们还能加入字符 \(c\) 去匹配 \(A\) 和 \(B\),但是 \(C\) 已经无法匹配了。
对于第一种,我们可以对上面的第一个条件进行修改:如果 \(pa_q<lstA_{pa_p}\) 且序列 \(B\) 的 \(dp_q\) 位加入 \(C_p\) 后位置仍然 \(<pb_p\),那么就满足第一种无解。这个也可以用线段树实现,因为 \(pa\) 递增,因此可以和 dp 放在一起,具体可以见代码。
对于第二种,我们考虑加入字符前的状态 \((p,dp_p)\)。
设 \(A\) 序列中字符 \(c\) 最后一次出现在 \(edA_c\),\(B,C\) 同理。
枚举加入的字符 \(c\),我们需要判断如下条件:\(edC_c\le p\),\(edA_c>pa_p\),\(edB_c>dp_p\)。
扫描线处理掉第一维,然后相当于查询一个后缀 \(\max\),可以树状数组维护之。
于是总复杂度 \(O(n\log n)\)。
//#include "hieroglyphs.h"
#include <bits/stdc++.h>
using namespace std;
const int inf = 1e9;
const int kN = 2e5 + 5, kS = kN * 4;
int n, m, t;
int a[kN], b[kN], c[kN];
namespace Task1 {
int mpa[kN], mpb[kN], rema[kN], remb[kN];
bool Solve() {
memset(mpa, 0, sizeof(mpa));
memset(mpb, 0, sizeof(mpb));
memset(rema, 0, sizeof(rema));
memset(remb, 0, sizeof(remb));
for(int i = 1; i <= n; i++) rema[a[i]]++;
for(int i = 1; i <= m; i++) remb[b[i]]++;
deque<int> pa, pb;
for(int i = 1; i <= n; i++) {
if(rema[a[i]] <= remb[a[i]]) {
pa.push_back(i);
}
}
for(int i = 1; i <= m; i++) {
if(remb[b[i]] <= rema[b[i]]) {
pb.push_back(i);
}
}
pa.push_back(n + 1);
pb.push_back(m + 1);
int ca = 0, cb = 0;
for(int i = 1; i < pa[0]; i++) rema[a[i]]--;
for(int i = 1; i < pb[0]; i++) remb[b[i]]--;
for(int i = 1; i <= pa[0]; i++) mpa[a[i]]++;
for(int i = 1; i <= pb[0]; i++) mpb[b[i]]++;
auto MatchA = [&](int x) -> void {
c[++t] = a[x];
pa.pop_front();
do mpb[b[++cb]]--;
while((cb <= m) && (b[cb] != a[x])) ;
while(ca < x) mpa[a[++ca]]--;
for(int i = x; i < pa[0]; i++) rema[a[i]]--;
for(int i = x + 1; i <= pa[0]; i++) mpa[a[i]]++;
};
auto MatchB = [&](int x) -> void {
c[++t] = b[x];
pb.pop_front();
do mpa[a[++ca]]--;
while((ca <= n) && (a[ca] != b[x])) ;
while(cb < x) mpb[b[++cb]]--;
for(int i = x; i < pb[0]; i++) remb[b[i]]--;
for(int i = x + 1; i <= pb[0]; i++) mpb[b[i]]++;
};
while(1) {
if((ca > n) || (cb > m)) return 0;
int x = pa[0], y = pb[0];
if((x > n) && (y > m)) break;
if(a[x] == b[y]) {
c[++t] = a[x];
while(ca < x) mpa[a[++ca]]--;
while(cb < y) mpb[b[++cb]]--;
pa.pop_front();
pb.pop_front();
for(int i = x; i < pa[0]; i++) rema[a[i]]--;
for(int i = y; i < pb[0]; i++) remb[b[i]]--;
for(int i = x + 1; i <= pa[0]; i++) mpa[a[i]]++;
for(int i = y + 1; i <= pb[0]; i++) mpb[b[i]]++;
continue;
}
bool fa = (mpb[a[x]] > 0), fb = (mpa[b[y]] > 0);
if(!fa && !fb) return 0;
if(!fa || !fb) {
if(!fa) MatchB(y);
else MatchA(x);
continue;
}
bool ga = (rema[a[x]] > remb[a[x]]);
bool gb = (remb[b[y]] > rema[b[y]]);
if(ga == gb) return 0;
if(ga) MatchA(x);
else MatchB(y);
}
return 1;
}
}
namespace Task2 {
int pa[kN], pb[kN], dp[kN];
int lsta[kN], lstb[kN], lstc[kN];
int eda[kN], edb[kN], edc[kN];
vector<int> vecb[kN];
#define ls (o << 1)
#define rs (o << 1 | 1)
struct SGT {
int mn[kS];
void Clear() {
memset(mn, 0x3f, sizeof(mn));
}
void Up(int o) { mn[o] = min(mn[ls], mn[rs]); }
void Modify(int o, int l, int r, int x, int v) {
if(l == r) return void(mn[o] = v);
int mid = (l + r) >> 1;
if(mid < x) Modify(rs, mid + 1, r, x, v);
else Modify(ls, l, mid, x, v);
Up(o);
}
int Query(int o, int l, int r, int x, int y) {
if((l > y) || (r < x)) return inf;
if((l >= x) && (r <= y)) return mn[o];
int mid = (l + r) >> 1;
return min(Query(ls, l, mid, x, y), Query(rs, mid + 1, r, x, y));
}
}sgt;
struct BIT {
int tr[kN];
void Clear() {
memset(tr, 0, sizeof(tr));
}
void Update(int x, int v) {
for(; x; x &= (x - 1)) tr[x] = max(tr[x], v);
}
int Query(int x) {
int ans = 0;
for(; x < kN; x += (x & -x)) ans = max(ans, tr[x]);
return ans;
}
}bit;
void Get(int *a, int *ed, int *lst, int n) {
memset(ed, 0, kN * sizeof(int));
fill(lst + 1, lst + n + 1, 0);
for(int i = 1; i <= n; i++) {
lst[i] = ed[a[i]];
ed[a[i]] = i;
}
}
void Match(int *a, int *c, int n, int t, int *p) {
for(int i = 1, j = 0; i <= t; i++) {
for(j++; a[j] != c[i]; j++) ;
p[i] = j;
}
}
int NextB(int p, int v) {
auto it = upper_bound(vecb[v].begin(), vecb[v].end(), p);
return (it == vecb[v].end()) ? inf : *it;
}
bool Check(int *a, int *b, int *c, int n, int m, int t) {
sgt.Clear();
memset(dp, 0x3f, sizeof(dp));
for(int i = 0; i < kN; i++) vecb[i].clear();
Get(a, eda, lsta, n);
Get(b, edb, lstb, m);
Get(c, edc, lstc, t);
Match(a, c, n, t, pa);
Match(b, c, m, t, pb);
for(int i = 1; i <= m; i++) vecb[b[i]].push_back(i);
dp[0] = 0;
for(int i = 1; i <= t; i++) {
int pre = sgt.Query(1, 1, n, pa[lstc[i]], lsta[pa[i]] - 1);
if((pre < inf) && (NextB(pre, c[i]) < pb[i])) return 0;
int mn = sgt.Query(1, 1, n, pa[lstc[i]], pa[i] - 1);
if(!lstc[i]) mn = 0;
if(mn <= m) dp[i] = min(dp[i], NextB(mn, c[i]));
sgt.Modify(1, 1, n, pa[i], dp[i]);
}
bit.Clear();
for(int i = 1; i <= t; i++) {
if(edc[c[i]] == i) bit.Update(eda[c[i]], edb[c[i]]);
if(bit.Query(pa[i] + 1) > dp[i]) {
return 0;
}
}
return 1;
}
}
vector<int> ucs(vector<int> A, vector<int> B) {
n = m = t = 0;
memset(a, 0, sizeof(a));
memset(b, 0, sizeof(b));
memset(c, 0, sizeof(c));
for(int x : A) a[++n] = x + 1;
for(int x : B) b[++m] = x + 1;
if(!Task1::Solve()) return vector<int> {-1};
if(!Task2::Check(a, b, c, n, m, t)) return vector<int> {-1};
if(!Task2::Check(b, a, c, m, n, t)) return vector<int> {-1};
vector<int> ans (t);
for(int i = 0; i < t; i++) ans[i] = c[i + 1] - 1;
return ans;
}
浙公网安备 33010602011771号