大毒瘤题。只会暴力。
题意:见题目名称。数据范围 \(10^5\)。
题解。
首先考虑区间去重这类东西一般怎么搞。参考 HH 的项链这题,我们离线询问,每扩展一次右端点便将右端点位置的值加 \(1\),并将右端点的元素上一次的出现位置的值减 \(1\),查询的时候就是区间和。
所以这个题也可以离线询问,每次扩展右端点时把所有在 \(r\) 结束的子串的左端点位置加 \(1\),并将它们之前出现的开头位置的权值减 \(1\),查询的时候仍然是区间和。
那么我们的第一个问题就是这个区间加减区间求和。显然线段树。
这个每次扩展右端点时带来的新贡献显然就是 \([1,r]\) 做一次区间加。考虑怎么做区间减。为了优化复杂度,我们需要以压缩的形式表示所有子串,想到使用 SAM。
SAM 上一个节点表示的所有字符串的上一次出现的位置结尾都是相同的,那么设这个位置是 \(end_i\),一个节点就可以使区间 \([end_i-len_i+1,end_i-len_{fa_i}]\) 减 \(1\)。那么一个暴力就是在 SAM 上爆跳,每次更新结束位置,每个节点上区间加减。
发现一个重要性质:每次修改都是把 SAM 的从根到某个节点的一条链的结束位置改成相同的。那么如果我们可以快速更新结束位置相同的一条链的贡献就可以求解。这个其实好做,假设链顶的父亲是 \(fa\),链底是 \(x\),那么要区间减的区间就是 \([end_x-len_x+1,end_x-len_{fa}]\)。如何保证链的数量?发现这个从根到某个节点扒出来一条链不就是 access 吗,所以使用 LCT。
具体地说,我们使用 LCT 维护 parent 树。首先建出 SAM 并记录每个前缀在 SAM 上的位置,并初始化 LCT ,所有边全是虚边。每次扩展一次右端点,考虑怎么更新一条链。我们对 access 做点手脚,每次和上面一棵 splay 连上的时候就可以进行上一段所说的区间更新。(所以这个 LCT 甚至只需要 access)
代码很长,但是全是板子。
#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cstring>
#define int long long
using namespace std;
int n,m,pos[100010],ans[200010];
char s[100010];
struct ques{
int l,r,id;
bool operator<(const ques&s)const{
return r<s.r;
}
}q[200010];
namespace SegTree{
#define lson rt<<1
#define rson rt<<1|1
struct node{
int l,r,sum,lzr;
}tree[400010];
void pushup(int rt){
tree[rt].sum=tree[lson].sum+tree[rson].sum;
}
void pushtag(int rt,int val){
tree[rt].sum+=(tree[rt].r-tree[rt].l+1)*val;
tree[rt].lzr+=val;
}
void pushdown(int rt){
if(tree[rt].lzr){
pushtag(lson,tree[rt].lzr);pushtag(rson,tree[rt].lzr);
tree[rt].lzr=0;
}
}
void build(int rt,int l,int r){
tree[rt].l=l;tree[rt].r=r;
if(l==r)return;
int mid=(l+r)>>1;
build(lson,l,mid);build(rson,mid+1,r);
}
void update(int rt,int l,int r,int val){
if(l<=tree[rt].l&&tree[rt].r<=r){
pushtag(rt,val);return;
}
pushdown(rt);
int mid=(tree[rt].l+tree[rt].r)>>1;
if(l<=mid)update(lson,l,r,val);
if(mid<r)update(rson,l,r,val);
pushup(rt);
}
int query(int rt,int l,int r){
if(l<=tree[rt].l&&tree[rt].r<=r)return tree[rt].sum;
pushdown(rt);
int mid=(tree[rt].l+tree[rt].r)>>1,val=0;
if(l<=mid)val+=query(lson,l,r);
if(mid<r)val+=query(rson,l,r);
return val;
}
#undef lson
#undef rson
}
namespace SAM{
int cnt=1,last=1,len[200010],fa[200010],trie[200010][26];
void ins(int ch,int ps){
int p=last;last=++cnt;
len[last]=len[p]+1;pos[ps]=last;
while(p&&!trie[p][ch])trie[p][ch]=cnt,p=fa[p];
if(!p){
fa[last]=1;return;
}
int q=trie[p][ch];
if(len[p]+1==len[q]){
fa[last]=q;return;
}
len[++cnt]=len[p]+1;
for(int j=0;j<26;j++)trie[cnt][j]=trie[q][j];
fa[cnt]=fa[q];fa[q]=cnt;fa[last]=cnt;
while(trie[p][ch]==q)trie[p][ch]=cnt,p=fa[p];
}
}
namespace LCT{
#define lson tree[x].son[0]
#define rson tree[x].son[1]
struct lct{
int fa,son[2],end,lz;
}tree[200010];
bool isroot(int x){
return tree[tree[x].fa].son[0]!=x&&tree[tree[x].fa].son[1]!=x;
}
bool get(int x){
return tree[tree[x].fa].son[1]==x;
}
void pushtag(int x,int val){
tree[x].end=tree[x].lz=val;
}
void pushdown(int x){
if(tree[x].lz){
if(lson)pushtag(lson,tree[x].lz);
if(rson)pushtag(rson,tree[x].lz);
tree[x].lz=0;
}
}
void rotate(int x){
int y=tree[x].fa,z=tree[y].fa,tmp=get(x);
if(!isroot(y))tree[z].son[tree[z].son[1]==y]=x;
tree[y].son[tmp]=tree[x].son[tmp^1];
if(tree[x].son[tmp^1])tree[tree[x].son[tmp^1]].fa=y;
tree[x].son[tmp^1]=y;
tree[y].fa=x;tree[x].fa=z;
}
void pushall(int x){
if(!isroot(x))pushall(tree[x].fa);
pushdown(x);
}
void splay(int x){
pushall(x);
for(int i=tree[x].fa;i=tree[x].fa,!isroot(x);rotate(x)){
if(!isroot(i))rotate(get(i)==get(x)?i:x);
}
}
void access(int ps){
int p,x=pos[ps];
for(p=0;x;p=x,x=tree[x].fa){
splay(x);rson=p;
if(tree[x].end){
SegTree::update(1,tree[x].end-SAM::len[x]+1,tree[x].end-SAM::len[tree[x].fa],-1);
}
}
SegTree::update(1,1,ps,1);
pushtag(p,ps);
}
}
signed main(){
scanf("%s%lld",s+1,&m);n=strlen(s+1);
for(int i=1;i<=m;i++){
scanf("%lld%lld",&q[i].l,&q[i].r);q[i].id=i;
}
sort(q+1,q+m+1);
SegTree::build(1,1,n);
for(int i=1;i<=n;i++)SAM::ins(s[i]-'a',i);
for(int i=1;i<=SAM::cnt;i++)LCT::tree[i].fa=SAM::fa[i];
int p=1;
for(int i=1;i<=n;i++){
LCT::access(i);
if(p>m)break;
while(p<=m&&q[p].r<=i){
ans[q[p].id]=SegTree::query(1,q[p].l,q[p].r);
p++;
}
}
for(int i=1;i<=m;i++)printf("%lld\n",ans[i]);
return 0;
}
标签:子串,end,SAM,int,位置,个数,本质,端点,区间
From: https://www.cnblogs.com/gtm1514/p/17100398.html