【51nod1600】Simple KMP(SAM)(LCT)(差分)

题意:

对于一个字符串 ∣ S ∣ |S| S,我们定义 f a i l [ i ] fail[i] fail[i],表示最大的 x 使得 S [ 1.. x ] = S [ i − x + 1.. i ] S[1..x]=S[i-x+1..i] S[1..x]=S[ix+1..i],满足 ( x < i ) (x<i) (x<i)
显然对于一个字符串,如果我们将每个 0 < = i < = ∣ S ∣ 0<=i<=|S| 0<=i<=S 看成一个结点,除了 i = 0 i=0 i=0 以外 i i i f a i l [ i ] fail[i] fail[i] 连边,这是一颗树的形状,根是 0
我们定义这棵树是 G ( S ) G(S) G(S),设 f ( S ) f(S) f(S) G ( S ) G(S) G(S) 中除了 0 号点以外所有点的深度之和
其中 0 号点的深度为 -1
定义 k e y ( S ) key(S) key(S) 等于 S S S 的所有非空子串 S ′ S' S f ( S ′ ) f(S') f(S) 之和
给定一个字符串S,现在你要实现以下几种操作:
1.在 S S S最后面加一个字符
2.询问 k e y ( S ) key(S) key(S)


好题!
考虑新加一个字符的贡献,就是考虑所有的后缀的新增贡献

注意到,某一个后缀的新增贡献,就是它往上跳一个 f a i l fail fail,然后剩下的就全部是跳上去过后的那个点的贡献,而这个点在之前作为后缀出现过,所以我们只需要算出一个增量的差分数组

每一个后缀的新增贡献是它在 G ( S ) G(S) G(S) 中的深度
注意到统计一棵树的深度和可以等价于统计每个点的 s i z e size size
也就是这个套路
∑ i i ∗ ∑ d e p = i 1 = ∑ i ∑ d e p ≥ i 1 \sum_ii*\sum_{dep=i}1=\sum_i\sum_{dep\ge i}1 iidep=i1=idepi1
又注意到这个就等价于对每一个后缀统计它的贡献,需要算它出现了多少次
就是把 f a i l fail fail 链提出来,对每个点的贡献就是 ( l e n [ x ] − l e n [ f a [ x ] ] ) ∗ s i z [ x ] (len[x]-len[fa[x]])*siz[x] (len[x]len[fa[x]])siz[x]
需要支持换父亲,链加, L C T LCT LCT 即可
感觉差分,从上一步走来,就像是站在巨人的肩膀上


#include<bits/stdc++.h>
#define cs const
using namespace std;
cs int N = 2e5 + 5;
cs int Mod = 1e9+7;
typedef long long ll;
int add(int a, int b){ return a+b>=Mod?a+b-Mod:a+b; }
int mul(int a, int b){ return (ll)a*b%Mod; }
void Add(int &a, int b){ a = add(a, b); }
int n; char s[N];
namespace LCT{
	int ch[N][2], fa[N], r[N], tg[N], len[N], sm[N], vl[N];
	bool isr(int x){ return ch[fa[x]][0] != x && ch[fa[x]][1] != x; }
	int get(int x){ return ch[fa[x]][1] == x; }
	void pushup(int x){
		sm[x]=sm[ch[x][0]]+len[x]+sm[ch[x][1]];
		vl[x]=add(add(vl[ch[x][0]],vl[ch[x][1]]),mul(r[x],len[x]));
	}
	void tag(int x, int v){ if(!x) return; Add(r[x],v); Add(tg[x],v); Add(vl[x],mul(sm[x],v)); }
	void down(int x){ if(tg[x]) tag(ch[x][0],tg[x]), tag(ch[x][1],tg[x]),tg[x]=0; }
	void path(int x){ if(!isr(x)) path(fa[x]); down(x); }
	void rot(int x){
		int y=fa[x],z=fa[y],k=get(x); if(!isr(y)) ch[z][get(y)]=x; fa[x]=z;
		ch[y][k]=ch[x][k^1]; fa[ch[x][k^1]]=y; ch[x][k^1]=y; fa[y]=x; pushup(y); pushup(x);
	}
	void spl(int x){ path(x); while(!isr(x)){ int y=fa[x]; if(!isr(y)) rot(get(x)^get(y)?x:y); rot(x); } }
	void acs(int x){ for(int y=0;x;y=x,x=fa[x]) spl(x),ch[x][1]=y,pushup(x); }
	void link(int x, int y){ acs(x); spl(x); fa[x] = y; }
	void cut(int x, int y){ acs(x); spl(x); spl(y); ch[y][1]=fa[x]=0; pushup(y); }
}
namespace SAM{
	int ch[N][26], lk[N], r[N], len[N], las=1, node=1;
	void init(int x){
		LCT::len[x]=len[x]-len[lk[x]];
		LCT::r[x]=r[x]; LCT::pushup(x);
	}
	int extend(int c){
		int now = ++node, p = las; len[now]=len[p]+1; r[now]=1;
		for(;p&&!ch[p][c];p=lk[p]) ch[p][c]=now;
		if(!p) lk[now]=1, init(now), LCT::link(now,1);
		else{
			int q = ch[p][c]; 
			if(len[q]==len[p]+1) lk[now]=q, init(now), LCT::link(now,q);
			else{
				int cl = ++node; 
				len[cl]=len[p]+1; lk[cl]=lk[q]; 
				memcpy(ch[cl],ch[q],sizeof(ch[q]));
				LCT::acs(q); LCT::spl(q);
				r[cl]=r[q]=LCT::r[q];
				init(cl);
				LCT::link(cl,lk[cl]);
				LCT::cut(q,lk[q]);
				lk[now]=lk[q]=cl;
				init(q); 
				init(now);
				LCT::link(now,lk[now]);
				LCT::link(q,lk[q]);
				for(;p&&ch[p][c]==q;p=lk[p]) ch[p][c]=cl;
			}
		} las = now;
		LCT::acs(now); LCT::spl(now); 
		int ps=LCT::ch[now][0], delta=LCT::vl[ps]; 
		LCT::tag(ps,1); return delta;
	}
}
int main(){
	scanf("%s",s+1); n=strlen(s+1);
	int res = 0, ans = 0;
	for(int i = 1; i <= n; i++){
		Add(res, SAM::extend(s[i]-'a'));
		Add(ans, res); cout << ans << '\n';
	} return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
 RGD 是一个下载工具,但是它不是应用于你的个人电脑的!   RGD 可以下载别的网站提供的东西直接到你的空间中,不用下载到本地然后再上传到自己的空间中,也就是是从A空间到B空间(可以是不同服务器上的,也可以使同一台服务器上的), 举个例子: shou.com提供下载一个软件 sohu.com/soft111.rar , 你可以将这个下载的软件直接下载到自己的空间里面的某个你设定的目录。这么说应该可以理解!   RGD 可以节省你的下载时间,如果你下载的东西在国外的空间上,而你自己的也是国外空间,那么它可以节省你多多一半的下载再上传的时间。假设你使用了一台没有按照FTP软件的电脑,那么有了RGD ,你就方便了,打开自己的装有RGD 的空间,输入你想要下载的软件(或其它)的下载地址连接,把你下载的东西先放在自己空间里保存好!   RGD 可以添加注释内容; 可以直接将下载的文件分割并存储; 可以发送电子邮件到你指定的邮箱;可以设置并使用代理服务器下载;可以指定下载保存目录。 它在线编辑功能: 可以批量更改文件名;可以批量分割文件;可以批量发送到电子邮件;可以批量删除! 注意:    1) 你的空间必须支持PHP (不需要MySQL), 如果是 Linux主机, 你所设定的下载目录的属性一定要改成777, windows主机不需要改!   2) 这是有一个管理工具,不是随便开放给每个人使用的,除非是你的好朋友!当然我的演示也除外!所以请更改下载以后的文件名称,改一个你能记住的,默认的是 a2b.php , 下载目录中的index.html不要删除或者你可以换一个别的内容的页面,这是为了防止别人看到你网页目录下内容的。   3) 如果你用 RGD 上传东西到你的空间里面了,最好 删除或重命名的时候也用 RGD 来操作,因为程序在空间里面生成了一个列表文件(files.lst), 如果你在FTP上删除或重命名文件了,那么列表示没有变化的。那么在RGD 里看到的内容要和实际的文件不一样了。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

FSYo

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值