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\) 就都可以删除不管,因此不妨认为当前两个序列都是还没开始匹配的状态。

先判掉一些平凡的情况:

  1. \(A_i=B_j\):这时只能匹配 \(A_i\)\(B_j\)
  2. \(A_{1\sim i}\neq B_j\):这时我们无法匹配 \(B_j\),只能匹配 \(A_i\)
  3. \(A_i\neq B_{1\sim j}\):同上,只能匹配 \(B_j\)
  4. \(B_{j\sim m}\)\(A_i\) 个数严格小于 \(A_{i\sim n}\):如果不去匹配 \(A_i\),那么后面肯定就匹配不完 \(A\) 了。因此匹配 \(A_i\)
  5. \(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\)

不妨设:

  1. \(A_{1\sim i-1}\)\(a\)\(1\)
  2. \(A_{i+1\sim n}\)\(b\)\(0\)
  3. \(B_{1\sim j-1}\)\(c\)\(0\)
  4. \(B_{j+1\sim m}\)\(d\)\(1\)

\(2,3\),我们有限制 \(a>0,c>0\)

那么我们可以构造出两个公共子序列:

  1. \(a\)\(1\),然后 \(b+1\)\(0\)
  2. \(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\) 不合法。

那么我们只剩下了两种情况:

  1. \(i\ge pa_p\)\(j\le pb_p\)
  2. \(i\le pa_p\)\(j\ge pb_p\)

同时我们可以发现,因为当前 \(D\) 的前缀是 \(C_{1\sim p}\) 的子序列,因此如果 \(i\ge pa_p\),那么我们让 \(i=pa_p\) 是可行的。于是两类就变成:

  1. \(i=pa_p\)\(j\le pb_p\)
  2. \(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\),需要满足的条件:

  1. 对于 \(A_{pa_q}\),往后加了一个 \(C_p\) 字符后可以匹配到 \(A_{pa_p}\),但是我们允许不匹配第一个位置。
  2. 对于 \(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;
}
posted @ 2025-07-18 14:21  CJzdc  阅读(3)  评论(0)    收藏  举报