P9576 「TAOI-2」Ciallo~(∠・ω< )⌒★

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

知识点:Z 函数,哈希,枚举,数数,序列数据结构

本质上是个串串枚举题,但是用序列数据结构优化了一下(

你说的对,但是:::

昨天晚上看到室友在玩一个叫千恋万花的游戏,我就好奇的凑上去看了下。他好像在推一个巫女样子的角色,白头发顶着对兽耳,于是我凑上去和他分享,我说「原神里也有巫女,比你这个好看多了」。

我室友让我闭嘴,别影响他打游戏。我想了想的确,影响别人打游戏是不好的,于是我就不说话了。

后来我室友换了个叫什么茉子的角色,是个忍者的样子,我看到了这是真的忍不住了。我立刻手机打开了原神,冲上去和我室友说到「原神里也有好多当忍者的角色诶」,说着将手机拿过去想给他看看...

不知道这家伙是不是因为剧情推不下去就火气很大,在这时唰的一下站了起来,用手不礼貌的指着我的脸大喊,能不能别用你那破原神影响我。我听着他的话感到一阵羞辱,我好心和他分享,他却如此这般。

想着这样自私的室友,我也就没了和他争吵的念头,我就回自己的座位去了。随后我只是在位子上嘀咕着,柚子厨是这样的,他就跟火药桶被点炸了一样,立刻冲过来夺了我的手机。

他掐着我的手机怼着我的脸,姿势就像握着板砖一样,很是吓人。他假装义正言辞的跟我说,让我别拿原神这种游戏来恶心他。我低着头一眼不发,他也渐渐放下了持着我手机的手臂。

僵持了半分钟,他说他平时说话糙了点,但是对人一直很友善,刚刚他走了单身线才这么冲动,希望我别生气。我一时无言以对,就说道「没事,原神里优菈也是...」

我话还没说完,他青筋又暴起来,一下子把我的手机摔在了地上...

说真的,现在我才认清我室友自私自利的嘴脸,根本不值得我给他分享好东西。

简述

给定两个仅由小写字母构成的字符串 \(s, t\),求有多少组 \(l, r, l', r'\),满足下列条件:

  • \(1\le l\le r\le |s|\)
  • \(s\) 删去子串 \(s[l:r]\) 后变为 \(s'\),有:\(1\le l'\le r'\le |s'|\)
  • \(s'[l':r'] = t\)

\(1\le |s|\le 4\times 10^5\)\(1\le |t|\le 2\times 10^5\)
2S,512MB。

分析

先解决一种简单的情况。若最终得到的 \(s'[l':r']\) 即为 \(s\) 中原来就是与 \(t\) 匹配的连续的子串 \(s[i:i + |t| - 1]\),则仅需确定删去的 \(l, r\),考虑 \(l,r\) 只可能同时出现在 \(s[i:i + |t| - 1]\) 的一侧且可以相等,显然方案数为 \(\frac{(i - 1)\times i}{2}+ \frac{(n - (i + |t| - 1))(n - (i + |t| - 1) + 1)}{2}\) 种。

然后考虑 最终得到的 \(s'[l':r']\)\(s\) 中不连续的情况,即 \(s'[l':r']\) 可以被拆成 \(t\) 的一段前缀 \(\operatorname{pre}\) 和一段后缀 \(\operatorname{suf}\),两者在 \(s\) 中保持前后关系且不相邻,考虑枚举其中的一方并求可以拼成 \(t\) 的另一方的数量。

\(\operatorname{p}_1(i)\) 表示后缀 \(s[i:|s|]\)\(t\) 的最长公共前缀长度,\(\operatorname{p}_2(i)\) 表示前缀 \(s[1:i]\)\(t\) 的最长公共后缀长度。 \(\operatorname{p}_1\)\(\operatorname{p} _2\) 可以通过对 \(s, t\) 正串和反串分别求 Z 函数预处理得到。为什么强调是前缀和后缀呢?因为要保证 \(\operatorname{pre}\)\(\operatorname{suf}\) 都不能是空的,这样限制方便下一步求贡献。

则以 \(s_i\) 为开头可以组成长度为 \(1\sim \operatorname{p}_1(i)\)\(\operatorname{pre}\)。对于以 \(s_i\) 开头长度为 \(l\)\(\operatorname{pre}\),设可以与之拼成 \(t\)\(\operatorname{suf}\)\(s_j\) 为结尾,则 \(j\) 需要满足:

\[\begin{cases} j \ge i + |t|\\ \operatorname{p}_2(j)\ge |t| - \operatorname{p}_1(i) \end{cases}\]

对于一个满足 \(j\ge i + |t|\)\(j\),考虑它可以对某个 \(i\) 产生多少贡献。为了保证 \(\operatorname{pre}\)\(\operatorname{suf}\) 都不能是空的,则有贡献的以 \(s_j\) 为结尾的 \(\operatorname{suf}\) 的长度限制为:

\[|t| - \operatorname{p}_1(i)\le |\operatorname{suf}|\le \operatorname{p}_2(j) \]

由上式可知,对于所有以以 \(s_i\) 开头的 \(\operatorname{pre}\),所有满足上述条件的 \(\operatorname{suf}\) 数量之和,也即所有 \(j\)\(i\) 的贡献之和即为:

\[\begin{aligned} &\sum_{j\ge i + |t|\land \operatorname{p}_2(j)\ge |t| - \operatorname{p}_1(i)} \operatorname{p}_2(j) - \left(|t| -\operatorname{p}_1(i)\right)\\ =& \left(\sum_{j\ge i + |t|\land \operatorname{p}_2(j)\ge |t| - \operatorname{p}_1(i)} \operatorname{p}_2(j)\right)- \left(|t| -\operatorname{p}_1(i)\right)\left(\sum_{j\ge i + |t|\land \operatorname{p}_2(j)\ge |t| - \operatorname{p}_1(i)} 1\right) \end{aligned}\]

上面括号里的两部分可以以相似的方式分别求出来。发现满足条件的 \(j\) 是一个二维偏序的形式,这东西显然可以通过序列数据结构维护,即考虑在倒序枚举 \(i\) 的同时维护两个初始为空的树状数组,枚举到 \(i(1\le i\le |s| - |t|)\) 时将位置 \(\operatorname{p}_2(i + |t|)\) 加上 \(\operatorname{p}_2(i + |t|)\text{ or } 1\),查询区间 \(\left[|t| - \operatorname{p}_1(i), |t| - 1\right]\) 中的权值和即得括号内的值。

总时间复杂度 \(O\left(|s|\log |t|\right)\) 级别。

所以这东西本质上就是个枚举嘛呃呃

代码

//Ciallo~(∠・ω< )⌒★
/*
By:Luckyblock
*/
#include <bits/stdc++.h>
#define LL long long
const int kN = 4e5 + 10;
//=============================================================
int n, m, z1[kN], z2[kN], p1[kN], p2[kN];
char s[kN], t[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;
}
struct Bit {
  #define lowbit(x) ((x)&-(x))
  int lim;
  LL t[kN];
  void Init(int n_) {
    lim = n_;
  }
  void Insert(int pos_, LL val_) {
    for (int i = pos_; i <= lim; i += lowbit(i)) {
      t[i] += val_;
    }
  }
  LL Sum(int pos_) {
    LL ret = 0;
    for (int i = pos_; i; i -= lowbit(i)) {
      ret += t[i];
    }
    return ret;
  }
  LL Query(int l_, int r_) {
    return Sum(r_) - Sum(l_ - 1);
  }
  #undef lowbit
} bit1, bit2;
void z_function() {
  z1[1] = m;
  for (int i = 2, l = 0, r = 0; i <= m; ++ i) {
    if (i <= r && z1[i - l + 1] < r - i + 1) {
      z1[i] = z1[i - l + 1];
    } else {
      z1[i] = std::max(0, r - i + 1);
      while (i + z1[i] <= m && t[z1[i] + 1] == t[i + z1[i]]) ++ z1[i];
    }
    if (i + z1[i] - 1 > r) l = i, r = i + z1[i] - 1;
  }
  for (int i = 1, l = 0, r = 0; i <= n; ++ i) {
    if (i <= r && z1[i - l + 1] < r - i + 1) {
      p1[i] = z1[i - l + 1];
    } else {
      p1[i] = std::max(0, r - i + 1);
      while (i + p1[i] <= n && s[i + p1[i]] == t[p1[i] + 1]) ++ p1[i];
    }
    if (i + p1[i] - 1 > r) l = i, r = i + p1[i] - 1;
  }

  std::reverse(t + 1, t + m + 1);
  std::reverse(s + 1, s + n + 1);
  z2[1] = m;
  for (int i = 2, l = 0, r = 0; i <= m; ++ i) {
    if (i <= r && z2[i - l + 1] < r - i + 1) {
      z2[i] = z2[i - l + 1];
    } else {
      z2[i] = std::max(0, r - i + 1);
      while (i + z2[i] <= m && t[z2[i] + 1] == t[i + z2[i]]) ++ z2[i];
    }
    if (i + z2[i] - 1 > r) l = i, r = i + z2[i] - 1;
  }
  for (int i = 1, l = 0, r = 0; i <= n; ++ i) {
    if (i <= r && z2[i - l + 1] < r - i + 1) {
      p2[i] = z2[i - l + 1];
    } else {
      p2[i] = std::max(0, r - i + 1);
      while (i + p2[i] <= n && s[i + p2[i]] == t[p2[i] + 1]) ++ p2[i];
    }
    if (i + p2[i] - 1 > r) l = i, r = i + p2[i] - 1;
  }
  std::reverse(p2 + 1, p2 + n + 1);
}
void Solve1() {
  for (int i = 1; i <= n; ++ i) {
    if (p1[i] == m) {
      int l1 = i - 1, l2 = n - (i + m) + 1;
      ans += 1ll * l1 * (l1 + 1) / 2ll;
      ans += 1ll * l2 * (l2 + 1) / 2ll;
    }
  }
}
void Solve2() {
  for (int i = 1; i <= n; ++ i) {
    if (p1[i] == m) -- p1[i];
    if (p2[i] == m) -- p2[i];
  }
  bit1.Init(m), bit2.Init(m);
  LL sum = 0;
  for (int i = n - m; i; -- i) {
    if (p2[i + m]) bit1.Insert(p2[i + m], 1);
    if (p2[i + m]) bit2.Insert(p2[i + m], p2[i + m]);

    LL ret1 = bit1.Query(m - p1[i], m - 1);
    LL ret2 = bit2.Query(m - p1[i], m - 1);
    sum = ret2 - ret1 * (m - p1[i] - 1);
    ans += sum;
  }
}
//=============================================================
int main() {
  // freopen("1.txt", "r", stdin);
  scanf("%s", s + 1); n = strlen(s + 1);
  scanf("%s", t + 1); m = strlen(t + 1);
  z_function();
  Solve1();
  Solve2();
  printf("%lld\n", ans);
  return 0;
}
posted @ 2024-01-20 20:54  Luckyblock  阅读(34)  评论(2编辑  收藏  举报