P7114 [NOIP2020] 字符串匹配

Link:https://www.luogu.com.cn/problem/P7114

知识点:枚举,结论,Z 函数,哈希

唉,三年了,三年!!!

简述

\(T\) 组数据,每组数据给定仅由小写字母组成的字符串 \(s\),求 \(t = {(AB)}^iC\) 的方案数,其中 \(F(A) \le F(C)\),其中 \(F(t)\) 表示字符串 \(t\) 中出现奇数次的字符的数量。两种方案不同当且仅当拆分出的 \(A\)\(B\)\(C\) 中有至少一个字符串不同。
对于所有测试点,保证 \(1 \le T \le 5\)\(1 \le |s| \le 2^{20}\)
1S,512MB。

分析

\(O(|s|\ln |s|\log 26)\) 考场垃圾做法

看到古老的代码了回忆一下。

首先预处理所有前缀和后缀中出现次数为奇数次的字符的数量,然后考虑枚举 \(AB\) 的长度,再枚举 \((AB)^i\) 的长度,可以通过字符串哈希判断枚举的 \((AB)^i\) 是否合法。确定了 \((AB)^i\)\(C\) 唯一确定,仅需求所有 \(A\) 的可行值中满足 \(F(A)\le F(C)\) 的数量,可通过树状数组维护 \(F(A)\) 求得。

枚举 \((AB)^i\) 的时间复杂度是调和级数,总时间复杂度为 \(O(|s|\ln |s|\log 26)\)

考场上写丑了清空居然全都是暴力 memset 并且树状数组值域开到了 \(N\)、、、其实把 memeset 换成枚举并且把树状数组值域改小可以概率卡过去。考场上写了什么几把啊我草幸亏没被卡多测清空好歹水到 84 分不然省一没了呜呜

\(O(n\log 26)\) 真算法

同样考虑枚举 \(AB\),但是接下来不暴力枚举 \((AB)^i\) 而是进一步深挖性质。

首先考虑以 \(AB\) 为循环节的最长前缀可以到什么位置,即求合法的 \((AB)^i\)\(i\) 的最大值。手玩了下发现可以通过求 \(s[|AB| + 1: n]\)\(s\) 的最长公共前缀,也即 Z 函数求得,满足:

\[\max\{i\} = \min\left\{ \left\lfloor \dfrac{z(|AB| + 1)}{|AB|} \right\rfloor + 1, \dfrac{|s| - 1}{|AB|} \right\} \]

注意 \(A, B, C\) 都要求非空,所以上式中有一个取 \(\min\) 防止出现 \(C\) 非空的情况。

然后考虑为什么要强调 \(F\) 代表出现次数为奇数的字符数量?手玩下发现 \((AB)^{2k}\) 中所有字符出现次数均为偶数,也即当 \(i\) 的变化量为偶数时 \(F(C)\) 是不变的,仅需讨论 \(i\) 为奇数与偶数的情况即可确定 \(F(C)\),再求有多少 \(A\) 合法即可,不需要再枚举 \((AB)^{i}\) 了。

\(i\) 为奇数的个数为 \(\left\lfloor\frac{\max\{i\} + 1}{2}\right\rfloor\),此时有 \(F(C) = F(s[|AB| + 1: |s|])\)\(i\) 为偶数的个数为 \(\left\lfloor\frac{\max\{i\}}{2}\right\rfloor\),此时有 \(F(C) = F(s[2\times |AB| + 1: |s|])\)。同样可通过树状数组维护 \(F(A)\) 求合法 \(A\) 的数量。

比考场做法少了枚举 \((AB)^i\) 的调和级数,总时间复杂度为 \(O(|s|\log 26)\) 级别。

已经有了不依赖于字符集大小纯线性的做法?牛逼,但是懒了。

代码

\(O(|s|\log 26)\) 真算法:

//
/*
By:Luckyblock
*/
#include <bits/stdc++.h>
#define LL long long
const int kN = 1e6 + 5e5 + 10;
const int kM = 30;
//=============================================================
int n, z[kN];
int cnt_pre[30], cnt_suf[30], sum_pre[kN], sum_suf[kN];
char s[kN];
LL ans;
//=============================================================
inline int read() {
  int f = 1, w = 0; char ch = getchar();
  for (; !isdigit(ch); ch = getchar()) if (ch == '-') f = -1;
  for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0'); 
  return f * w;
}
namespace Bit {
  #define lowbit(x) ((x)&-(x))
  int time, t[kM], tim[kM];
  void Init() {
    ++ time;
  }
  void Insert(int pos_) {
    ++ pos_;
    for (int i = pos_; i <= 27; i += lowbit(i)) {
      if (tim[i] != time) t[i] = 0, tim[i] = time;
      t[i] ++;
    }
  }
  int Sum(int pos_) {
    ++ pos_;
    int ret = 0;
    for (int i = pos_; i; i -= lowbit(i)) {
      if (tim[i] != time) t[i] = 0, tim[i] = time; 
      ret += t[i];
    }
    return ret;
  }
  #undef lowbit
}
void z_function() {
  z[1] = n;
  for (int i = 2, l = 1, r = 1; i <= n; ++ i) {
    if (i <= r && z[i - l + 1] < r - i + 1) {
      z[i] = z[i - l + 1];
    } else {
      z[i] = std::max(0, r - i + 1);
      while (i + z[i] <= n && s[z[i] + 1] == s[i + z[i]]) ++ z[i];
    }
    if (i + z[i] - 1 > r) l = i, r = i + z[i] - 1;
  }
}
void Init() {
  ans = 0;
  scanf("%s", s + 1); n = strlen(s + 1);
  for (int i = 0; i <= 27; ++ i) cnt_pre[i] = cnt_suf[i] = 0;
  for (int i = 1; i <= n + 1; ++ i) sum_pre[i] = sum_suf[i] = 0;

  for (int i = 1; i <= n; ++ i) {
    ++ cnt_pre[s[i] - 'a'];
    if (cnt_pre[s[i] - 'a'] % 2 == 1) sum_pre[i] = sum_pre[i - 1] + 1;
    else sum_pre[i] = sum_pre[i - 1] - 1;
  }
  for (int i = n; i >= 1; -- i) {
    ++ cnt_suf[s[i] - 'a'];
    if (cnt_suf[s[i] - 'a'] % 2 == 1) sum_suf[i] = sum_suf[i + 1] + 1;
    else sum_suf[i] = sum_suf[i + 1] - 1;
  }

  z_function();
  Bit::Init();
}
void Solve() {
  for (int ab = 2; ab <= n; ++ ab) {
    int cnt = std::min(z[ab + 1] / ab + 1, (n - 1) / ab);
    Bit::Insert(sum_pre[ab - 1]);
    ans += 1ll * (cnt + 1) / 2ll * Bit::Sum(sum_suf[ab + 1]);
    ans += 1ll * cnt / 2ll * Bit::Sum(sum_suf[1]);
  }
}
//=============================================================
int main() {
  // freopen("1.txt", "r", stdin);
  int T = read();
  while (T --) {
    Init();
    Solve();
    printf("%lld\n", ans);
  }
  return 0;
}
/*
1
aaaaaa

1
aaaa
*/

\(O(|s|\ln |s|\log 26)\) 考场垃圾做法:

以下为考场代码。

//
/*
By:Luckyblock

fuck

ccf

//freopen

我知道会有人看见的。  

这里是一个已退役选手,在结束前 10 分钟的随想。  

近 2 年的喜怒哀乐,在脑中缓缓流淌。  

我曾妄想:挥斥方遒,信步走出考场—— 

终不如愿, 

别了, 

我愿为你鼓掌。 

Goodbye,World!
*/
#include <algorithm>
#include <cctype>
#include <cstdio>
#include <cstring>
#define LL long long
const int kN = 1e6 + 4e5 + 10;
const int mod1 = 998244353;
const int mod2 = 1e9 + 7;
const int kBase = 23333;
//=============================================================
int n, cnt_pre[30], cnt_suf[30], sum_pre[kN], sum_suf[kN];
int pw1[kN], pw2[kN], has1[kN], has2[kN];
LL ans;
char s[kN];
//=============================================================
int read() {
  int f = 1, w = 0; char ch = getchar();
  for (; !isdigit(ch); ch = getchar()) {
    if (ch == '-') f = -1;
  }
  for (; isdigit(ch); ch = getchar()) {
    w = 10 * w + ch - '0';
  }
  return f * w;
}
namespace Bit {
  #define low(x) (x&-x)
  int t[kN];
  void Init() {
    memset(t, 0, sizeof (t));
  }
  void Insert(int pos_) {
    for (int i = pos_; i <= n; i += low(i)) {
      t[i] ++;
    }
  }
  int Sum(int pos_) {
    int ret = 0;
    for (int i = pos_; i; i -= low(i)) {
      ret += t[i];
    }
    return ret;
  }
  #undef low
}
void Init() {
  ans = 0;
  Bit::Init();
  memset(cnt_pre, 0, sizeof (cnt_pre));
  memset(cnt_suf, 0, sizeof (cnt_suf));
}
bool Judge(int r1_, int l2_, int r2_) {
  int h1 = (1ll * (1ll * has1[r2_] - 1ll * has1[l2_ - 1] * pw1[r2_ - l2_ + 1] % mod1) % mod1 + mod1) % mod1;
  int h2 = (1ll * (1ll * has2[r2_] - 1ll * has2[l2_ - 1] * pw2[r2_ - l2_ + 1] % mod2) % mod2 + mod2) % mod2;
  return ((h1 == has1[r1_]) && (h2 == has2[r1_]));
}
void Prepare() {
  Init();
  scanf("%s", s + 1);
  n = strlen(s + 1);
  
  for (int i = 1; i <= n; ++ i) {
    int now = s[i] - 'a';
    cnt_pre[now] ++;
    if (cnt_pre[now] % 2 == 1) {
      sum_pre[i] = sum_pre[i - 1] + 1;
    } else {
      sum_pre[i] = sum_pre[i - 1] - 1;
    }
    
    has1[i] = 1ll * (1ll * has1[i - 1] * kBase % mod1 + now) % mod1;
    has2[i] = 1ll * (1ll * has2[i - 1] * kBase % mod2 + now) % mod2;
  }
  for (int i = n; i; -- i) {
    int now = s[i] - 'a';
    cnt_suf[now] ++;
    if (cnt_suf[now] % 2 == 1) {
      sum_suf[i] = sum_suf[i + 1] + 1;
    } else {
      sum_suf[i] = sum_suf[i + 1] - 1;
    }
  }
}
//=============================================================
int main() {
  int T = read();
  pw1[0] = pw2[0] = 1;
  for (int i = 1; i <= kN - 10; ++ i) {
    pw1[i] = 1ll * pw1[i - 1] * kBase % mod1;
    pw2[i] = 1ll * pw2[i - 1] * kBase % mod2;
  }
  while (T --) {
    Prepare();
    for (int ab = 2; ab < n; ++ ab) {
      Bit::Insert(sum_pre[ab - 1] + 1);
      ans += 1ll * Bit::Sum(sum_suf[ab + 1] + 1);
      for (int c = 2 * ab; c + 1 <= n; c += ab) {
        if (! Judge(ab, c - ab + 1, c)) break;
        ans += 1ll * Bit::Sum(sum_suf[c + 1] + 1);
      }
    }
    printf("%lld\n", ans);
  }
  return 0;
}
posted @ 2024-01-21 09:50  Luckyblock  阅读(76)  评论(0)    收藏  举报