首页 > 其他分享 >P7114 [NOIP2020] 字符串匹配

P7114 [NOIP2020] 字符串匹配

时间:2024-01-21 09:55:36浏览次数:38  
标签:suf AB P7114 int kN 枚举 ch 字符串 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;
}

标签:suf,AB,P7114,int,kN,枚举,ch,字符串,NOIP2020
From: https://www.cnblogs.com/luckyblock/p/17977540

相关文章

  • 22String字符串和vector对象的迭代器iterator实现
    String字符串对象的迭代器iterator实现泛型算法参数接收的都是迭代器泛型算法是一组全局的函数,适用于所有容器基于第二点,泛型算法有一套方法可以统一地遍历所有容器的元素classString{public: //嵌套定义iterator类 classiterator { private: char*_p;//没有用......
  • 代码随想录算法训练营第 十 一 天| 20. 有效的括号 1047. 删除字符串中的所有相邻重
    LeetCode 20.有效的括号题目链接:20.有效的括号思路:采用栈数据结构解题;遇到左括号,压右括号入栈 LeetCode 1047.删除字符串中的所有相邻重复项题目链接:1047.删除字符串中的所有相邻重复项注意:Java中队列实现类API的使用 LeetCode 150.逆波兰表达式求值题目链......
  • 在JavaScript中减去一个日期时间字符串的两分钟
    例如:js将2024-01-2003:18:38减两分钟的到:2024-01-2003:16:38 functionsubtractTwoMinutes(dateString){//解析日期时间字符串为Date对象constdate=newDate(dateString);//减去两分钟date.setMinutes(date.getMinutes()-2);......
  • 无涯教程-MATLAB - 字符串(Strings)
    在MATLAB中创建字符串非常简单,实际上,我们已经使用了很多次。例如,您在命令提示符下键入以下内容-my_string='LearnfkPoint'MATLAB将执行上述语句并返回以下输出-my_string=LearnfkPointMATLAB将所有变量视为数组,而字符串则视为字符数组,让我们使用whos命令检查上面创建的变......
  • Go中的整数到字符串的转换
    在Go语言中,我们经常需要将整数转换为字符串。然而,直接使用string()函数进行转换可能会导致意想不到的结果。这是因为string()函数会将整数解释为Unicode字符的代码点,而不是将其转换为对应的数字字符串。错误的转换方式例如,如果我们尝试将整数65转换为字符串:s := stri......
  • .NET字符串内存管理:常量字符串、动态创建和字符串池的巧妙结合
     在.NET中,字符串是不可变的,这意味着一旦创建,字符串的内容就不能被修改。字符串在内存中以不同的方式存储,具体取决于它是常量字符串还是动态创建的字符串。常量字符串常量字符串在编译时就被解析,并在程序的元数据(Metadata)中存储。多个相同的字符串常量可能会共享同一块内存......
  • C# 字符串操作指南:长度、连接、插值、特殊字符和实用方法
    字符串用于存储文本。一个字符串变量包含由双引号括起的字符集合示例://创建一个string类型的变量并赋予一个值stringgreeting="Hello";如果需要,一个字符串变量可以包含多个单词:示例:stringgreeting2="Nicetomeetyou!";字符串长度在C#中,字符串实际上是一......
  • 每日一题 2024-1-20 按分隔符拆分字符串
    1.题目(1239)原题链接给你一个字符串数组\(words\)和一个字符\(separator\),请你按\(separator\)拆分\(words\)中的每个字符串。返回一个由拆分后的新字符串组成的字符串数组,不包括空字符串。注意\(separator\)用于决定拆分发生的位置,但它不包含在结果字符串中。拆分......
  • python之字符串二
    字符串详解                   1. indexdefindex(self,sub,start=None,end=None):#realsignatureunknown;restoredfrom__doc__"""S.index(sub[,start[,end]])->intReturnthelowestindexinSwhere......
  • 洛谷题单指南-模拟和高精度-P1098 [NOIP2007 提高组] 字符串的展开
    原题链接:https://www.luogu.com.cn/problem/P1098题意解读:题目本身是一道模拟题,但是细节点较多,要拿100分,有以下注意点:1、-号两个需要同时为小写字母或者数字,才进行填充2、-号左边>=右边,直接输出-3、对待填充的内容的处理,可以先看是否填充*;小写字母和数字的填充都是前一位asci......