【luogu P6629】字符串(Runs)(树状数组)

字符串

题目链接:luogu P6629

题目大意

给你一个字符串,然后多次询问每次问你一个子串,问你这个串有多少个本质不同的平方子串。

思路

md 到底是什么阴间东西。

本原平方串

其实这个东西就是本原平方串。
首先一些简单的性质:ab 前缀,ap 的循环节,bq 的,p|qq<|a|,那 b 也有 p 的循环节。

然后如果 st 的前缀(都非空),而且 2|s|>t,那 |t||s|s 的周期。
这个画个图不难理解,就是看多出来那一部分。

然后证一些东西:

  1. 如果 u,v,w 非空且 uuvv 前缀,vvww 前缀,uu 是本原平方串,那 |u|+|v|<|w|

首先如果 |w|2|v| 显然成立,所以只看 |w|<2|v|
然后可以得到 |w||v|v 周期。
然后设 |u|+|v|>|w|(反证),即 |w||v|<|u|,所以 |w||v| 也是 u 周期。
然后分类 2|u||v|,那 uuv 前缀,就有 |w||v| 也是 uu 周期。
但是 |w||v|<|u|,就更小了,所以不行。
如果 2|u|>|v|,我们就有 |v||u|u 的周期。

然后你看看:v 周期有 |w||v|,|u||u| 这个你画个图看看就会发现了)
然后再把 w 表示成 vs1u=s1s2v=us3w=s1s2s3s1
然后你把 uu,vv,ww 列出来,会发现 |s2|s2s3 周期,然后还会有周期 |v||u|=|s3|
(别问,问就是看不懂照抄)

然后你 WPL 一下,就会 s2s3 周期有 gcd(s2,s3)
然后你用最上面的性质也会有这也是 u 的前缀。
然后 u 又有周期 |w||r|=|s1|,然后又 WPL 就有周期 gcd(s1,s2,s3)
那这个肯定是小于一半的,所以就矛盾了。

(别骂了看不懂硬胡的)


然后根据上面的性质有一些结论:

  1. 一个串的本原平方串的个数不超过 O(|s|log|s|)

考虑开头位置,如果有两个 uu,vv,下一个至少为上一个长度的两倍(好像说最垃是斐波那契,不知道为啥),然后是 log 级别的。

  1. 一个串的本质不同的本原平方串个数是 O(n)

考虑开头位置,如果有三个 uu,vv,ww,就会有 |w||u|+|v|2|u|,所以 uuw 开头就出现,肯定是在别的地方之前出现过,所以每个位置开头至多两个不同的。


然后我们不难看出,一个本原平方串一定是属于一个 run 的一部分,就本原平方串一定是 run,然后还可以扩展。

那我们找本原平方串就可以从这里下手:
我们枚举 run 找对于的本原平方串,那 run r=(l,r,p)[l,r] 里面任意一个长度为 2p 的子串都是本原平方串,然后找的复杂度是 r=(l,r,p)Runs(s)(rl+12p+1),然后得到是 O(nlogn) 级别。

找本质不同的

好的真正阴间的部分来了。
(毕竟前面可以记结论)
(好吧是我菜)

我们考虑把一个本原平方串按左右坐标放在二维平面上,那每次询问就是二维数点。
那考虑到问题为什么难求,因为可能有重复的,考虑容斥掉(其实也没有什么好容斥的,就是你构造一种方法使得减剩一个)
那就是你考虑到相同的有两种可能:同一个 run 里面的,不同的 run 之间的。

先看同一个 run 里面的,那发现一定是形成一个斜率 45° 的线。
那就考虑从第二个点开始在它 y 轴的位置上一个点 x 轴的位置放一个 1,那这样如果碰到了第二个必然也会碰到这个消去的。
然后你发现这个消去的也是斜线。
或者还有一种方法就是这里用的,你会发现它其实是两部分(就对于一个 2p),然后两部分之间会有,那我们就限制至多要一边的。

然后考虑不同的 run 之间的,就直接看哪个斜线是不是同一个(可以用 map 判),然后如果是就前面的最后一个跟后面的第一个之间像前面的方法一样放点,这个直接单独放即可。
然后至于斜线怎么二维偏序数点就拆成两个射线,然后神笔维护一下即可。
(至于维护可以看代码/fad)

代码

#include<map> #include<cstdio> #include<vector> #include<algorithm> #define ll long long using namespace std; const int N = 2e5 + 1000; int n, q, ans[N], ly[N], sta[N], m, fn; char s[N]; struct node { int l, r, id; }qs[N]; struct RUNS { int l, r, p; }runs[N]; struct HASH { const int di1 = 13331, di2 = 131; const int mo1 = 1e9 + 7, mo2 = 1e9 + 9; ll mi1[N], hash1[N], mi2[N], hash2[N]; void start() { mi1[0] = mi2[0] = 1; for (int i = 1; i <= n; i++) { mi1[i] = mi1[i - 1] * di1 % mo1; mi2[i] = mi2[i - 1] * di2 % mo2; hash1[i] = (hash1[i - 1] * di1 + s[i] - 'a' + 1) % mo1; hash2[i] = (hash2[i - 1] * di2 + s[i] - 'a' + 1) % mo2; } } pair <int, int> get(int l, int r) { return make_pair((hash1[r] - hash1[l - 1] * mi1[r - l + 1] % mo1 + mo1) % mo1, (hash2[r] - hash2[l - 1] * mi2[r - l + 1] % mo2 + mo2) % mo2); } }H; map <pair <int, int>, int> mp; int getl(int x, int y) { int l = 1, r = min(x, y), re = 0; while (l <= r) { int mid = (l + r) >> 1; if (H.get(x - mid + 1, x) == H.get(y - mid + 1, y)) re = mid, l = mid + 1; else r = mid - 1; } return re; } int getr(int x, int y) { int l = 1, r = min(n - x + 1, n - y + 1), re = 0; while (l <= r) { int mid = (l + r) >> 1; if (H.get(x, x + mid - 1) == H.get(y, y + mid - 1)) re = mid, l = mid + 1; else r = mid - 1; } return re; } bool cmpS(int x, int y) { int pl = getr(x, y); return s[x + pl] < s[y + pl]; } void Lyndon(bool op) { ly[n] = n; sta[0] = 0; sta[++sta[0]] = n + 1; sta[++sta[0]] = n; for (int i = n - 1; i >= 1; i--) { while (sta[0] > 1 && cmpS(i, sta[sta[0]]) == op) sta[0]--; sta[++sta[0]] = i; ly[i] = sta[sta[0] - 1] - 1; } } void slove(int x, int y) { int l = getl(x, y), r = getr(x, y); if (l + r >= y - x + 1) runs[++m] = (RUNS){x - l + 1, y + r - 1, y - x}; } bool cmp_run(RUNS x, RUNS y) { if (x.l != y.l) return x.l < y.l; if (x.r != y.r) return x.r < y.r; return x.p < y.p; } void get_runs() { for (int op = 0; op <= 1; op++) { Lyndon(op); for (int i = 1; i < n; i++) { slove(i, ly[i] + 1); } } sort(runs + 1, runs + m + 1, cmp_run); } void Init() { H.start(); get_runs(); int tmp = m; m = 0; for (int i = 1; i <= tmp; i++) if (runs[i].l != runs[i - 1].l || runs[i].r != runs[i - 1].r) { runs[++m] = runs[i]; } } bool cmp_qr(node x, node y) { return x.r < y.r; } struct SZSZ { ll f[N], sum; void add(int x, int y) { sum += y; for (; x <= n + n; x += x & (-x)) f[x] += y; } ll ask(int x) { ll re = 0; for (; x; x -= x & (-x)) re += f[x]; return re; } }T; vector <pair<int, int> > ed[N]; vector <RUNS> run[N]; void slove() { sort(qs + 1, qs + q + 1, cmp_qr); for (int x = 1; x <= m; x++) { run[runs[x].r].push_back(runs[x]); int l = runs[x].l, r = runs[x].r, p = runs[x].p; for (int i = l + 2 * p - 1; i <= r; i++) ed[i].push_back(make_pair(l, 2 * p)); } int now = 1; for (int i = 1; i <= n; i++) { for (int j = 0; j < ed[i].size(); j++) { int l = ed[i][j].first, p = ed[i][j].second; int pl = i - ((i - l + 1) / p * p) + 1; pair <int, int> now = H.get(pl, i); if (mp[now]) { T.add(mp[now], -1); mp[now] = 0; } } while (now <= q && qs[now].r <= i) { ans[qs[now].id] = T.sum - T.ask(qs[now].l - 1); for (int j = 0; j < ed[i].size(); j++) { int l = max(qs[now].l, ed[i][j].first); int t = (qs[now].r - l + 1) / ed[i][j].second; int ql = qs[now].r - ed[i][j].second / 2 + 1;//这个是内部相同的限制 if (t) { int lef = max(ql, t * ed[i][j].second + l - 1); ans[qs[now].id] += (qs[now].r - lef + 1) * t; if (lef > ql) ans[qs[now].id] += (lef - ql) * (t - 1); } } now++; } for (int j = 0; j < run[i].size(); j++) { RUNS p = run[i][j]; for (int r = p.r - p.p + 1; r <= p.r; r++) for (int l = r - 2 * p.p + 1; l >= p.l; l -= 2 * p.p) { pair <int, int> now = H.get(l, r); mp[now] = l; T.add(l, 1); } } } } int main() { scanf("%d %d", &n, &q); scanf("%s", s + 1); for (int i = 1; i <= q; i++) { scanf("%d %d", &qs[i].l, &qs[i].r); qs[i].id = i; } Init(); slove(); for (int i = 1; i <= q; i++) printf("%d\n", ans[i]); return 0; }

__EOF__

本文作者あおいSakura
本文链接https://www.cnblogs.com/Sakura-TJH/p/luogu_P6629.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   あおいSakura  阅读(116)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示