专栏文章

题解:P11150 [THUWC 2018] 字胡串

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

文章操作

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

当前评论
1 条
当前快照
1 份
快照标识符
@minmciye
此快照首次捕获于
2025/12/02 04:45
3 个月前
此快照最后确认于
2025/12/02 04:45
3 个月前
查看原文
20251014 更新:修正了 Z 函数的实现;用更严谨的方法证明了关键结论;新增了关于如何在正确的复杂度下进行排序预处理的说明。
只需要使用 Z 函数的单 log\log 解法,不依赖于字符集大小。
考虑固定 BB,比较从 x,yx, y 插入谁更优(x<yx < y)。删除掉公共的前后缀可知等价于比较 B+Ax+1yB + A_{x + 1 \sim y}Ax+1y+BA_{x + 1 \sim y} + B 的字典序。
有一个经典结论:比较 A+BA + BB+AB + A 的字典序,等价于比较 A+A^{+\infty}B+B^{+\infty} 的字典序。
证明:考虑用生成函数表示字符串。设 A+BA + B 对应的生成函数为 F(x)=A(x)+xAB(x)F(x) = A(x) + x^{|A|} B(x)B+AB + A 对应的生成函数为 G(x)=B(x)+xBA(x)G(x) = B(x) + x^{|B|} A(x)。则:
F(x)G(x)=A(x)+xAB(x)B(x)xBA(x)=A(x)(1xB)B(x)(1xA)=(1xA)(1xB)(A(x)1xAB(x)1xB)\begin{aligned} F(x) - G(x) &= A(x) + x^{|A|} B(x) - B(x) - x^{|B|} A(x) \\ &= A(x) (1 - x^{|B|}) - B(x) (1 - x^{|A|}) \\ &= (1 - x^{|A|}) (1 - x^{|B|}) (\frac {A(x)} {1 - x^{|A|}} - \frac {B(x)} {1 - x^{|B|}}) \end{aligned}
因为 (1xA)(1xB)(1 - x^{|A|}) (1 - x^{|B|}) 的常数项等于 11,所以 F(x)G(x)F(x) - G(x) 的最低次非 00 项系数等于 A(x)1xAB(x)1xB\frac {A(x)} {1 - x^{|A|}} - \frac {B(x)} {1 - x^{|B|}} 的最低次非 00 项系数。所以,比较 A+BA + BB+AB + A 对应的字典序,等价于比较 A(x)1xA\frac {A(x)} {1 - x^{|A|}} 对应的字符串和 B(x)1xB\frac {B(x)} {1 - x^{|B|}} 对应的字符串的字典序,即比较 A+A^{+\infty}B+B^{+\infty} 的字典序。
所以,xxyy 优当且仅当 B+Ax+1y+B^{+\infty} \leq A_{x + 1 \sim y}^{+\infty}
考虑如果对 B+B^{+\infty} 扫描线,则答案单调不降。考虑先把询问按照 B+B^{+\infty} 排序,再决策单调性分治。问题转化为求单组询问的答案。如果可以 O(A+B)\mathcal O(|A| + |B|) 解决单组询问,那整个问题的复杂度就是单 log\log
考虑如何高效地比较 B+Ax+1yB + A_{x + 1 \sim y}Ax+1y+BA_{x + 1 \sim y} + B 的字典序。如果 yx<By - x < |B|,那么问题比较容易解决:先比较 B1yxB_{1 \sim y - x}Ax+1yA_{x + 1 \sim y},预处理 B+#+AB + \texttt{\#} + A 的 Z 函数后可以 O(1)\mathcal O(1) 判断;如果相等,再比较 Byx+1BB_{y - x + 1 \sim |B|}B1B(yx)B_{1 \sim |B| - (y - x)},同样可以 O(1)\mathcal O(1) 判断;如果还相等,最后比较 Ax+1y=B1yxA_{x + 1 \sim y} = B_{1 \sim y - x}BB(yx)+1BB_{|B| - (y - x) + 1 \sim |B|},仍然可以 O(1)\mathcal O(1) 判断。
如果 yxBy - x \geq |B|,设 Ax+1y=Bk+CA_{x + 1 \sim y} = B^k + C,其中 BB 不是 CC 的前缀,则问题等价于比较 B+CB + CC+BC + B 的字典序,仍然可以使用 yx<By - x < |B| 的情况的方法解决。至于如何求出 kk,双指针即可。
还剩下最后一个问题:如何高效地把询问按照 B+B^{+\infty} 排序。这个问题的解决办法并不简单,因为比较 A+A^{+\infty}B+B^{+\infty} 的字典序的复杂度是 O(A+B)\mathcal O(|A| + |B|)。为了方便描述复杂度,设 LL 表示询问串的长度之和。有两种常用的解决办法(感谢 skip2004 的指导):
  • 严格 O(Llogn)\mathcal O(L \log n):因为 O(A+B)=O(max(A,B))\mathcal O(|A| + |B|) = \mathcal O(\max(|A|, |B|)),考虑把询问串按照长度从小到大插入到平衡树中,插入一个串时需要与 O(logn)\mathcal O(\log n) 个短串比较,所以复杂度是 O(Llogn)\mathcal O(L \log n)。可以用 multiset 实现。
  • 期望 O(Llogn)\mathcal O(L \log n):直接归并排序复杂度不对的原因是一个元素可能与多个元素进行比较,如果这个元素恰好是一个长串,那复杂度就退化成了 O(Ln)\mathcal O(Ln)。但是我们可以通过随机化来避免出现这种极端情况,先随机打乱再归并排序即可。可以用 stable_sort 实现。
    • 注:还有另外一种期望 O(Llogn)\mathcal O(L \log n) 的算法,即直接快速排序,在排序过程中随机选 pivot。这样做虽然期望复杂度正确,但是复杂度方差很大,容易被卡,不建议使用。
在本题的测试数据下,期望 O(Llogn)\mathcal O(L \log n) 的算法常数更小。下面是一份可能的代码实现:
CPP
#include <bits/stdc++.h>
using namespace std;

#define rep(i, a, b) for(int i = (a); i <= (b); ++i)
#define sz(x) (int)(x).size()
using ll = long long;

const int N = 1e6 + 10;
int n, q;
string a;
pair<string, int> qry[N];
int ans[N];

int z[2 * N];

void get_z(const string &s) {
  int l, r = 1;
  rep(i, 2, sz(s) - 1) {
    if(i <= r && z[i - l + 1] <= r - i) {
      z[i] = z[i - l + 1];
      continue;
    }
    z[i] = max(0, r - i + 1);
    while(s[z[i] + 1] == s[z[i] + i]) ++z[i];
    l = i, r = i + z[i] - 1;
  }
}

bool cmp(const string &b, int lo, int x, int y) {
  int len = sz(b) - 1;
  int t = z[sz(b) + x - lo + 1];
  if(t < min(y - x, len)) return b[t + 1] < a[x + t + 1];
  assert(y - x < len);
  t = z[y - x + 1];
  if(t < len - (y - x)) return b[y - x + t + 1] < b[t + 1];
  t = z[len - (y - x) + 1];
  if(t < y - x) return b[t + 1] < b[len - (y - x) + t + 1];
  return true;
}

void solve(int l, int r, int lo, int hi) {
  if(l > r) return;
  int mid = (l + r) >> 1;
  string b = " " + qry[mid].first;
  get_z(b + "#" + a.substr(lo + 1, hi - lo));
  int len = sz(b) - 1;
  int res = lo, j = lo;
  rep(i, lo + 1, hi) {
    if(i - j >= len && z[sz(b) - lo + j + 1] >= len) j += len;
    if(j < i && !cmp(b, lo, j, i)) res = j = i;
  }
  ans[qry[mid].second] = res;
  solve(l, mid - 1, lo, res);
  solve(mid + 1, r, res, hi);
}

mt19937_64 rng;

int main() {
  cin.tie(0)->sync_with_stdio(0);
  cin >> n >> q >> a, a = " " + a;
  rep(i, 1, q) {
    cin >> qry[i].first;
    qry[i].second = i;
  }
  shuffle(qry + 1, qry + q + 1, rng);
  stable_sort(qry + 1, qry + q + 1, [&](const auto &x, const auto &y) -> bool {
    return x.first + y.first < y.first + x.first;
  });
  solve(1, q, 0, n);
  rep(i, 1, q) cout << ans[i] << "\n";
}

评论

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

正在加载评论...