专栏文章

题解:P11291 【MX-S6-T3】「KDOI-11」简单的字符串问题 2

P11291题解参与者 2已保存评论 1

文章操作

快速查看文章及其快照的属性,并进行相关操作。

当前评论
1 条
当前快照
1 份
快照标识符
@mir447d1
此快照首次捕获于
2025/12/04 15:26
3 个月前
此快照最后确认于
2025/12/04 15:26
3 个月前
查看原文
提供一个巨大复杂的场内做法。
由于允许 RiR_i 为空,所以对于子串 T[l,r]T_{[l, r]},记 kk' 为其至少需要多少个好的字符串拼接而成,则 k[k,K]\forall k \in [k', K](l,r,k)(l, r, k) 均为好的三元组。
考虑贪心求解 kk':记 T[i,T]T_{[i, |T|]}nn 个串的 lcp\text{lcp} 的最大值为 pip_i,从 ll 开始每次跳最远的右端点即可求解。
具体地,记 ansl,ians_{l, i} 表示 T[l,i]T_{[l, i]} 对应的 kk'。为了对于一个左端点 ll 求出所有 [l,r][l, r]ansans,考虑维护一个 tt,初始为 l1l - 1,每次在 [l,t+1][l, t + 1] 范围内取一个 ii,最大化 q=i+piq = i + p_i,然后令 ansl,[t+1,q1]stepsans_{l, [t + 1, q - 1]} \gets \text{steps},再令 tq1t \gets q - 1,其中 steps\text{steps} 为操作次数,如果 q1tq - 1 \leq t 那么提前终止,否则在 q=T+1q = |T| + 1 时终止。
容易发现从初始状态跳跃一次之后,剩余的问题是一个几乎独立的子问题。形式化地讲,先判掉两种边界情况,第一种是 l+pl=T+1l + p_l = |T| + 1 的情况,此时有 ansl,=1ans_{l, *} = 1,第二种是 pl=0p_l = 0 的情况,此时不妨令 ansl,=K+1ans_{l, *} = K + 1。对于一般情况,我们求出一个 i[l+1,l+pl]i \in [l + 1, l + p_l],最大化 i+pii + p_i(多个任取其一),那么可以用下述方式计算 ansans 序列:
  • ansl,jmin(ansi,j+1,K+1),jians_{l, j} \gets \min(ans_{i, j} + 1, K + 1), j \geq i
  • 然后令 ansl,j1,lj<l+plans_{l, j} \gets 1, l \leq j < l + p_l
为了更有利于后续处理,我们将这个过程精细化一点:
  • 显然 ansans 序列是不降的,记 xx 为最小的位置满足 ansi,x=K+1ans_{i, x} = K + 1,若没有则为 T+1|T| + 1。假定 ansans 序列初始全 00
  • ansl,jansi,j,jians_{l, j} \gets ans_{i, j}, j \geq i
  • ansl,[l,i1]ans_{l, [l, i - 1]}11
  • ansl,[l+pl,x1]ans_{l, [l + p_l, x - 1]}11,如果 l+plx1l + p_l \leq x - 1
这意味着,我们只需要支持将 anslans_l 继承 ansians_i,以及区间加两种操作,很难不想到用线段树处理。乍一看继承操作得用主席树,但是事实上我们可以把继承操作建树,从而离线处理掉。
具体来说,对于每个“anslans_l 继承 ansians_i”的操作,我们连一条 ili \to l 的边,这样我们可以得到一个森林。考虑从根结点开始 dfs,每进入一个点时,我们分两种情况讨论:
  • 点是根结点:这对应了上文 ansl,=1ans_{l, *} = 1ansl,=K+1ans_{l, *} = K + 1 的情况,直接在线段树上对区间 [l,T][l, |T|] 区间加 1/K+11/K + 1 即可。
  • 否则:线段树二分出上文中的 xx,然后对 [l,i1][l, i - 1][l+pl,x1][l + p_l, x - 1] 区间加 11 即可。
当我们从一个点回溯时,只需要撤销加入时的贡献即可。这样一来我们可以做到 Θ(TlogT)\Theta(|T| \log |T|) 求出所有 ansans
考虑统计答案。统计好的三元组数量是容易的,对于每个 ll 合法的三元组数为 rl(K+1ansl,r)=(Tl+1)(K+1)rlansl,r\sum \limits_{r \geq l} (K + 1 - ans_{l, r}) = (|T| - l + 1)(K + 1) - \sum \limits_{r \geq l} ans_{l, r},而 ansl,r\sum ans_{l, r} 容易用线段树求出。对所有 ll 的答案求和即可。
第二问会棘手一些,考虑将 lirl \leq i \leq r 这一限制容斥,即总三元组数减去 [l,r][1,i1][l, r] \subseteq [1, i - 1] 的三元组数,再减去 [l,r][i+1,T][l, r] \subseteq [i + 1, |T|] 的三元组数。
[l,r][i+1,T][l, r] \subseteq [i + 1, |T|] 的三元组数是容易计算的,实质上就是求每个左端点 ll 对应的三元组数量,直接用上一问中的求法即可。
[l,r][1,i1][l, r] \subseteq [1, i - 1] 的限制下,我们需要对每个 rr 求出三元组数。实质上只需对于所有 ans,rans_{*, r} 求和,可以使用线段树维护历史和解决。
至此我们以 Θ(TlogT)\Theta(|T| \log |T|) 但是巨大常数的复杂度解决了问题。
关于代码:作者偷懒用了二分哈希求 lcp\text{lcp},所以会多出来一个 Θ(nTlogS)\Theta(n |T| \log|S|) 的复杂度,当然肯定是可以用字符串算法做到更优的。
CPP
// Such a destiny was not desired.
#include <bits/stdc++.h>
using namespace std;
using ll = long long;

constexpr int N = 5e5 + 5, Maxl = 5e4 + 5;
int n, K;
char S[12][Maxl]; int LS[12];
char T[N]; int m;
ll res;

constexpr int mod[2] = {998244353, 1004535809};
constexpr int base[2] = {31, 29};
int Pow[2][N];
inline pair<int, int> add(pair<int, int> H, char ch) {
  int x = H.first, y = H.second;
  x = (1ll * x * base[0] + (ch - 'a' + 1)) % mod[0];
  y = (1ll * y * base[1] + (ch - 'a' + 1)) % mod[1];
  return {x, y};
}
struct Hash {
  char *s; int n;
  vector<pair<int, int>> w;
  inline void build() {
    w.resize(n + 1);
    for(int i = 1; i <= n; i++) {
      w[i] = add(w[i - 1], s[i]);
    }
  }
  inline pair<int, int> getH(int l, int r) {
    int x = w[r].first, y = w[r].second;
    x = (mod[0] + x - 1ll * w[l - 1].first * Pow[0][r - l + 1] % mod[0]) % mod[0];
    y = (mod[1] + y - 1ll * w[l - 1].second * Pow[1][r - l + 1] % mod[1]) % mod[1];
    return {x, y};
  }
} HS[12], HT;

int p[N];

struct SparseTable {
  pair<int, int> st[19][N];
  inline void build() {
    for(int i = 1; i <= m; i++) {
      st[0][i] = {p[i] + i, i};
    }
    for(int i = 1; i <= 18; i++) {
      for(int j = 1; j + (1 << i) - 1 <= m; j++) {
        st[i][j] = max(st[i - 1][j], st[i - 1][j + (1 << i - 1)]);
      }
    }
  }
  inline pair<int, int> query(int l, int r) {
    int L = __lg(r - l + 1);
    return max(st[L][l], st[L][r - (1 << L) + 1]);
  }
} st;

// constexpr int maxn = 1000;
// int ans[maxn][maxn];
ll sumL[N], sumR[N];

vector<int> G[N];
int op[N], to[N];

struct SegmentTree {
  int t;
  ll tagc[N << 2], taga[N << 2], c[N << 2], a[N << 2], maxa[N << 2];
  inline void pushdown(int pos, int l, int r) {
    if(taga[pos] == 0 && tagc[pos] == 0) return;
    int mid = (l + r) >> 1;
    c[pos << 1] += tagc[pos] * (mid - l + 1);
    c[pos << 1 | 1] += tagc[pos] * (r - mid);
    a[pos << 1] += taga[pos] * (mid - l + 1);
    a[pos << 1 | 1] += taga[pos] * (r - mid);
    maxa[pos << 1] += taga[pos];
    maxa[pos << 1 | 1] += taga[pos];

    taga[pos << 1] += taga[pos]; 
    taga[pos << 1 | 1] += taga[pos];
    tagc[pos << 1] += tagc[pos];
    tagc[pos << 1 | 1] += tagc[pos];
    tagc[pos] = taga[pos] = 0;
  }
  inline void pushup(int pos) {
    c[pos] = c[pos << 1] + c[pos << 1 | 1]; 
    a[pos] = a[pos << 1] + a[pos << 1 | 1];
    maxa[pos] = max(maxa[pos << 1], maxa[pos << 1 | 1]);
  }
  inline void modify_a(int l, int r, int L, int R, ll k, int pos) {
    if(L <= l && r <= R) {
      a[pos] += (r - l + 1) * k;
      taga[pos] += k;
      maxa[pos] += k;
      return;
    }
    int mid = (l + r) >> 1;
    pushdown(pos, l, r);
    if(L <= mid) modify_a(l, mid, L, R, k, pos << 1);
    if(R > mid) modify_a(mid + 1, r, L, R, k, pos << 1 | 1);
    pushup(pos);
  }
  inline void modify_c(int l, int r, int L, int R, ll k, int pos) {
    if(L <= l && r <= R) {
      c[pos] += (r - l + 1) * k;
      tagc[pos] += k;
      return;
    }
    int mid = (l + r) >> 1;
    pushdown(pos, l, r);
    if(L <= mid) modify_c(l, mid, L, R, k, pos << 1);
    if(R > mid) modify_c(mid + 1, r, L, R, k, pos << 1 | 1);
    pushup(pos);
  }
  inline ll query_a(int l, int r, int L, int R, int pos) {
    if(L <= l && r <= R) return a[pos];
    int mid = (l + r) >> 1; ll ret = 0;
    pushdown(pos, l, r);
    if(L <= mid) ret += query_a(l, mid, L, R, pos << 1);
    if(R > mid) ret += query_a(mid + 1, r, L, R, pos << 1 | 1);
    pushup(pos);
    return ret;
  }
  inline ll query_c(int l, int r, int L, int R, int pos) {
    if(L <= l && r <= R) return c[pos] + t * a[pos];
    int mid = (l + r) >> 1; ll ret = 0;
    pushdown(pos, l, r);
    if(L <= mid) ret += query_c(l, mid, L, R, pos << 1);
    if(R > mid) ret += query_c(mid + 1, r, L, R, pos << 1 | 1);
    pushup(pos);
    return ret;
  }
  inline void modify(int l, int r, int L, int R, int x, int pos = 1) {
    modify_a(l, r, L, R, x, 1);
    modify_c(l, r, L, R, -1ll * t * x, 1);
  }
  inline int get(int l = 1, int r = m, int pos = 1) {
    if(l == r) return l;
    int mid = l + r >> 1; pushdown(pos, l, r);
    if(maxa[pos << 1] == K + 1) return get(l, mid, pos << 1);
    return get(mid + 1, r, pos << 1 | 1);
  }
} sgt;

void dfs(int i) {
  int o;
  if(!to[i]) sgt.modify(1, m, i, m, op[i]);
  else {
    sgt.modify(1, m, i, to[i] - 1, 1);
    o = (sgt.maxa[1] == K + 1 ? sgt.get() : m + 1);
    if(i + p[i] <= o - 1) sgt.modify(1, m, i + p[i], o - 1, 1);
  }
  sgt.t++;
  ll w = 1ll * (m - i + 1) * (K + 1) - sgt.query_a(1, m, i, m, 1);
  sumR[i] = w, res += w;
  // for(int j = i; j <= m; j++) {
  //   sumL[j] += K + 1 - sgt.query_a(1, m, j, j, 1);
  // }
  for(auto u : G[i]) {
    dfs(u);
  }

  if(!to[i]) sgt.modify(1, m, i, m, -op[i]);
  else {
    sgt.modify(1, m, i, to[i] - 1, -1);
    if(i + p[i] <= o - 1) sgt.modify(1, m, i + p[i], o - 1, -1);
  }
}

int main() {
  // freopen("string.in", "r", stdin);
  // freopen("string.out", "w", stdout);
  ios::sync_with_stdio(false);
  cin.tie(nullptr), cout.tie(nullptr);
  
  Pow[0][0] = Pow[1][0] = 1;
  for(int i = 1; i < N; i++) {
    for(int o = 0; o < 2; o++) {
      Pow[o][i] = 1ll * Pow[o][i - 1] * base[o] % mod[o];
    }
  }

  int _; cin >> _ >> n >> K;
  for(int i = 1; i <= n; i++) {
    cin >> S[i] + 1;
    LS[i] = strlen(S[i] + 1);
    HS[i].s = S[i], HS[i].n = LS[i], HS[i].build();
  }
  cin >> T + 1; m = strlen(T + 1);
  HT.s = T, HT.n = m, HT.build();

  for(int i = 1; i <= m; i++) {
    for(int j = 1; j <= n; j++) {
      int l = 1, r = min(m - i + 1, LS[j]), res = 0;
      while(l <= r) {
        int mid = l + r >> 1;
        if(HS[j].w[mid] == HT.getH(i, i + mid - 1)) {
          res = mid;
          l = mid + 1;
        } else {
          r = mid - 1;
        }
      }
      p[i] = max(p[i], res);
    }
  }
  st.build();
  
  // int res = 0;
  for(int i = m; i >= 1; i--) {
    if(p[i] == 0) {
      op[i] = K + 1;
      // fill(ans[i] + i, ans[i] + m + 1, K + 1);
    } else {
      if(i + p[i] > m) op[i] = 1;
      else {
        int t = (st.query(i + 1, i + p[i])).second;
        G[t].push_back(i);
        to[i] = t;
      }
      // for(int j = 1; j <= m; j++) ans[i][j] = ans[t][j] + 1;
      // for(int j = i; j < i + p[i]; j++) ans[i][j] = 1;
    }
    // for(int j = i; j <= m; j++) {
    //   res += max(0, K + 1 - ans[i][j]);
    //   sumL[j] += max(0, K + 1 - ans[i][j]);
    //   sumR[i] += max(0, K + 1 - ans[i][j]);
    // }
  }
  for(int i = m; i >= 1; i--) {
    if(to[i]) continue;
    dfs(i);
  }
  for(int i = 1; i <= m; i++) {
    sumL[i] = 1ll * (K + 1) * i - sgt.query_c(1, m, i, i, 1);
  }

  cout << res << "\n";
  for(int i = 1; i <= m; i++) sumL[i] += sumL[i - 1];
  for(int i = m; i >= 1; i--) sumR[i] += sumR[i + 1];
  for(int i = 1; i <= m; i++) {
    cout << res - sumL[i - 1] - sumR[i + 1] << " \n"[i == m];
  }
}

评论

1 条评论,欢迎与作者交流。

正在加载评论...