题目描述

给定字符串\(S(|S|\le10^5)\),对其每个前缀求出如下的统计量:

对该字符串中的所有子串,统计其出现的次数,求其平方和。

Sample Input:

aaa

Sample Output:


详细题解

1、只求整个串的答案的解决方案

首先可一眼想到后缀自动机。

对后缀自动机上每个状态,定义endpos为所有能走到该状态的子串中子串右端点的取值集合。如果求出其endpos位置个数\(x\),那么就能求得该状态对答案的贡献,为\(x^2*(r-l+1)\),其中\(l,r\)分别为该状态的最小和最大子串长度。\(l,r\)直接由状态的len得到。考虑每个结点endpos如何求。

定理:字符串\(S\)建出的自动机上,\(S\)的每个前缀走到的结点一定是非拷贝结点。

证明:当建出前缀\(S_i\)的自动机时,显然串\(S_i\)走到的结点是非拷贝结点。该结点的len值为\(|S_i|\)。若该结点被拷贝,由后缀自动机的性质,拷贝结点的len值严格小于原先结点的len值,因此串\(|S_i|\)走到的结点在之后不可能变化到拷贝结点,否则将与拷贝结点的len值将大于等于\(|S_i|\)。矛盾,证毕。

定理:字符串\(S\)建出的自动机上,将\(S\)的每个前缀走到的结点标记为1后,每个状态的endpos大小就是该状态对应自动机fail树子树中标记为1的结点个数。

证明:考虑一个子串\(s\)到达某状态\(ST\),那么必然可以将\(s\)左侧添加字符串\(t\)使得\(ts\)是\(S\)的一个前缀。那么\(ts\)所到达的自动机状态必然在\(ST\)状态的fail树子树中(由后缀自动机定义)。因此所有endpos位置都对应了fail树子树中一个标记为1的结点。另一方面,对两个不同的标记为1的结点,对应的endpos位置一定不同。因此fail树子树中标记为1的结点个数不重不漏的对应了endpos集合大小。证毕。

根据以上两个定理,建出自动机后,dfs fail树即可在线性时间内求出答案。

2、转化为数据结构问题

下面考虑当自动机添加一个字符时如何更新答案,这只需考虑哪些状态的endpos个数变多以及哪些状态\(l,r\)改变。

(1)endpos变化:

情况1:没有拷贝结点,则fail树中新的last结点直接指向某结点\(j\),last沿fail指针一路走上去的这些结点endpos大小加1。

情况2:有拷贝结点,则结点\(j\)子树被断开,\(j\)和新的last指向copy,copy指向\(j\)原来的link。仍然是last沿fail指针一路走上去的结点endpos+1。此外,copy结点具有\(j\)结点的所有endpos。

(2)\(l,r\)变化:

首先要考虑新的last以及copy这两个新结点。其次,当结点\(j\)子树被断开,然后\(j\)的link指向copy结点后,\(j\)的\(l,r\)区间会变化。除此之外没有别的结点。

于是问题转化为一条树链endpos大小加1,单点修改\(r,l\)的值,询问所有状态的\(endpos^2 \times (r-l+1)\)的和(称其为endpos的加权平方和,\(r-l+1\)为权)。又由于需要支持树的断开和合并,因此必须使用LinkCutTree。

3、问题的解决

首先我们需要考虑区间加1,询问全局平方和如何做。这是经典线段树题目,需要维护区间和,并用区间和更新区间平方和即可。

现在转到树中,只需要把线段树换成splay。splay中维护endpos值、\(r-l+1\)值(即权)、权的和、endpos的加权和以及加权平方和。

当链上加1后,endpos的加权和增量是权的和,而加权平方和增量可用endpos加权和以及权的和表示。

当修改\(j\)结点的\(r-l+1\)时,只需将\(j\)对应splay旋转到根,然后可直接对该结点访问或修改(不会影响其他结点)。

最后考虑如何求全局和。对于树链修改,只需记录修改前后树链增量即可。对于修改\(j\)结点的\(r-l+1\)以及添加copy结点,这合起来不会改变答案。

因此总时间复杂度\(O(n(|\sum|+\log n)\)。

核心代码

由于此题代码过长,在此略去LCT部分模板,值保留关键部分的代码供理解算法思想。代码如下:

 #define LL long long
struct Tree{
Tree *left, *right, *fa;
int endpos, len, delta;
LL sumLen, sum, sum2;
}lct[MAXN];
int num;
inline Tree* newNode(int endpos, int len){
lct[++num] = { lct, lct, lct, endpos, len, , len, (LL)endpos * len, (LL)endpos * endpos * len };
return &lct[num];
}
inline void pushUp(Tree * rt){
Tree *t1 = rt->left, *t2 = rt->right;
LL endpos = rt->endpos;
rt->sumLen = rt->len + t1->sumLen + t2->sumLen;
rt->sum = endpos * rt->len + t1->sum + t2->sum;
rt->sum2 = endpos * endpos * rt->len + t1->sum2 + t2->sum2;
}
inline void update(Tree *t, int delta){
t->delta += delta;
t->endpos += delta;
t->sum2 += * delta*t->sum + t->sumLen*delta*delta;
t->sum += t->sumLen*delta;
}
inline void pushDown(Tree *rt){
if (rt->delta){
if (rt->left != lct)update(rt->left, rt->delta);
if (rt->right != lct)update(rt->right, rt->delta);
rt->delta = ;
}
}
LL add(Tree *rt)
{
Tree *t = access(rt);
LL val = t->sum2;
update(t, );
return t->sum2 - val;
}
void changeLen(Tree *rt, int len)
{
LL endpos = rt->endpos, t = len - rt->len;
rt->sum2 += t * endpos * endpos;
rt->sum += t * endpos;
rt->sumLen += t;
rt->len = len;
}
LL add(char ch)
{
int c = convert(ch);
int cur = ++cnt, i;
st[cur].len = st[last].len + ;
memset(st[cur].next, , sizeof(st[cur].next));
for (i = last; i != - && !st[i].next[c]; i = st[i].link)
st[i].next[c] = cur;
if (i == -){
st[cur].link = ;
newNode(, st[cur].len);
}
else{
int j = st[i].next[c];
if (st[i].len + == st[j].len){
st[cur].link = j;
newNode(, st[cur].len - st[j].len);
link(&lct[cur], &lct[j]);
}
else{
int copy = ++cnt;
st[copy].len = st[i].len + ;
memcpy(st[copy].next, st[j].next, sizeof(st[j].next));
st[copy].link = st[j].link;
for (; i != - && st[i].next[c] == j; i = st[i].link)
st[i].next[c] = copy;
st[j].link = st[cur].link = copy;
cut(&lct[j]);
//cut后lct[j]是splay的根,对元素的修改和访问可直接进行
changeLen(&lct[j], st[j].len - st[copy].len);
newNode(, st[cur].len - st[copy].len);
newNode(lct[j].endpos, st[copy].len - st[st[copy].link].len);
link(&lct[j], &lct[copy]);
link(&lct[cur], &lct[copy]);
link(&lct[copy], &lct[st[copy].link]);
}
}
last = cur;
return add(&lct[cur]);
}
char s[];
int main()
{
scanf("%s", s);
LL ans = ;
init();
for (int i = ; s[i]; i++){
ans += add(s[i]);
printf("%lld\n", ans);
}
}

Sum of Squares of the Occurrence Counts解题报告(后缀自动机+LinkCutTree+线段树思想)的更多相关文章

  1. poj2528线段树解题报告,离散化+线段树

    题目网址:http://poj.org/problem?id=2528 题意: n(n<=10000)个人依次贴海报,给出每张海报所贴的范围li,ri(1<=li<=ri<=1 ...

  2. [BZOJ2946][Poi2000]公共串解题报告|后缀自动机

    鉴于SAM要简洁一些...于是又写了一遍这题... 不过很好呢又学到了一些新的东西... 这里是用SA做这道题的方法 首先还是和两个字符串的一样,为第一个字符串建SAM 然后每一个字符串再在这个SAM ...

  3. [codevs3160]最长公共子串解题报告|后缀自动机

    给出两个由小写字母组成的字符串,求它们的最长公共子串的长度. 样例就觉得不能更眼熟啊...好像之前用后缀数组做过一次 然后发现后缀自动机真的好好写啊...(当然当时学后缀数组的时候也这么认为... 这 ...

  4. [HEOI2016/TJOI2016] 排序 解题报告(二分答案/线段树分裂合并+set)

    题目链接: https://www.luogu.org/problemnew/show/P2824 题目描述: 在2016年,佳媛姐姐喜欢上了数字序列.因而他经常研究关于序列的一些奇奇怪怪的问题,现在 ...

  5. K-th occurrence HDU - 6704 (后缀数组+二分线段树+主席树)

    大意: 给定串s, q个询问(l,r,k), 求子串s[l,r]的第kk次出现位置. 这是一篇很好的题解: https://blog.csdn.net/sdauguanweihong/article/ ...

  6. 【LeetCode】1085. Sum of Digits in the Minimum Number 解题报告(C++)

    作者: 负雪明烛 id: fuxuemingzhu 个人博客:http://fuxuemingzhu.cn/ 目录 题目描述 题目大意 解题方法 遍历 日期 题目地址:https://leetcode ...

  7. 【LeetCode】1022. Sum of Root To Leaf Binary Numbers 解题报告(Python)

    作者: 负雪明烛 id: fuxuemingzhu 个人博客: http://fuxuemingzhu.cn/ 目录 题目描述 题目大意 解题方法 DFS 日期 题目地址:https://leetco ...

  8. 【LeetCode】977. Squares of a Sorted Array 解题报告(C++)

    作者: 负雪明烛 id: fuxuemingzhu 个人博客: http://fuxuemingzhu.cn/ 目录 题目描述 题目大意 解题方法 排序 日期 题目地址:https://leetcod ...

  9. 【LeetCode】363. Max Sum of Rectangle No Larger Than K 解题报告(Python)

    作者: 负雪明烛 id: fuxuemingzhu 个人博客: http://fuxuemingzhu.cn/ 题目地址: https://leetcode.com/problems/max-sum- ...

随机推荐

  1. python_24_test

    product_list=[ ('Iphone',5800), ('Mac Pro',9800), ('Bike',800), ('Watch',10600), ('Coffee',31), ('Py ...

  2. 优化通过redis实现的一个抢红包流程【下】

    上一篇文章通过redis实现的抢红包通过测试发现有严重的阻塞的问题,抢到红包的用户很快就能得到反馈,不能抢到红包的用户很久(10秒以上)都无法获得抢红包结果,起主要原因是: 1.用了分布式锁,导致所有 ...

  3. kubernetes-服务发现service(九)

    service •防止Pod失联    •定义一组Pod的访问策略    •支持ClusterIP,NodePort以及LoadBalancer三种类型    •Service的底层实现主要有ipta ...

  4. 国产中标麒麟Linux部署dotnet core 环境并运行项目 (二) 部署运行控制台项目

    背景 在上一篇文章安装dotnet core,已经安装好dotnet core了.之前只是安装成功了dotnet, 输入dotnet --info,可以确认安装成功了,但是在运行代码时,还是报错了,本 ...

  5. vue 采坑

    1.ref 在父组件中访问子组件实例,或者直接操作DOM元素时需要ref <input ref="ipt"> 通过this.$refs.ipt 得到此input $re ...

  6. C++ 无限定名称查找

    无限定名称查找 (关键字:懒惰,挑捡,using指令的特殊性) 无限定名称查找实际上就是指没有限定(名称空间和名称空间运算符)名存在的一个名字的出现,其中对于using指令,其内部包含的所有的声明是被 ...

  7. Eclipse 发布 JAR

    明确要生成何种类型 jar 生成工具 jar,作为包被其他程序调用 具体步骤: 选中项目文件,点右键选择 Export ,JAR File 在弹出窗口选择,导出哪些文件,并且选择好 输出 JAR 的路 ...

  8. P4744 A’s problem(a)

    时间: 1000ms / 空间: 655360KiB / Java类名: Main 背景 冬令营入学测试题,每三天结算一次成绩.参与享优惠 描述 这是一道有背景的题目,小A也是一个有故事的人.但可惜的 ...

  9. 用户和用户组以及 Linux 权限管理

    1.从 /etc/passwd 说起 前面的基本命令学习中,我们介绍了使用 passwd 命令可以修改用户密码.对于操作系统来说,用户名和密码是存放在哪里的呢?我们都知道一个站点的用户名和密码是存放在 ...

  10. python flask学习第1天

    flask安装: 第一个flask程序: 用pycharm新建一个flask项目,新建项目的截图如下: app.py代码如下: #从flask这个包中导入Flask这个类 #Flask这个类是项目的核 ...