P4384 [八省联考 2018] 制胡窜(SAM)

Foreword \text{Foreword} Foreword

人都道正难则反,我偏说正也不难。

这里介绍一种正面直接统计的做法。
和补集做法相比,没有那么多的分类讨论,更多的是对问题的正向分析和逐层化简、转化,也并不麻烦。
由于需要写很多线段树的操作,码量较大,本人在210行左右(但都是较为基础的操作,写起来很舒适)。
本来这就是一道码农题不是吗!
我们开始吧。

Solution \text{Solution} Solution

前置规定:
以下设截下的串的两端点(即题面描述 i + 1 , j − 1 i+1,j-1 i+1,j1)为 x , y x,y x,y x , y ∈ ( 1 , n ) x,y\in(1,n) x,y(1,n)
设询问串长度为 l e n len len
s ( a , b ) = ∑ i = a b i s(a,b)=\sum_{i=a}^bi s(a,b)=i=abi a > b a>b a>b 时,规定 s ( a , b ) = 0 s(a,b)=0 s(a,b)=0

(以下基本模拟我做本题的思维过程)

SA 和本题感觉不是很搭,那基本就是 SAM 了。
给的限制乍一看非常恶心,但细想想还挺有话说的。

Part 1

设询问串第一次出现的右端点 位置为 a a a,最后一次出现左端点 位置为 b b b,那么当中间截下的字符串 x > a x>a x>a y < b y<b y<b 的时候,必然是合法的。
SAM 反串先建出后缀树,记录出现的最早和最晚位置并向父亲传递,即可维护这两个值。
容易统计这个时候的答案,应该是:
s ( 1 , n − a − 1 ) + s ( 1 , b − 2 ) − s ( 1 , ( b − 1 ) − ( a + 1 ) + 1 ) s(1,n-a-1)+s(1,b-2)-s(1,(b-1)-(a+1)+1) s(1,na1)+s(1,b2)s(1,(b1)(a+1)+1)
最后减去的是为了容斥掉左右端点同时满足条件算重的方案数。

Part 2

那么现在的问题就转化为了:

x ≤ a , y ≥ b x\le a,y\ge b xa,yb 且包含询问串的字符串 ( x , y ) (x,y) (x,y) 对数。

这个东西并太不好做。
暴力怎么做?
暴力是不是我们固定一个 x x x,然后设左端点在 x x x 右侧的最靠左的字符串的右端点 p o s pos pos,那么 r r r 的取值范围就是 [ max ⁡ ( b , p o s ) , n − 1 ] [\max(b,pos),n-1] [max(b,pos),n1]
随着左端点右移, p o s pos pos 单调不升,右端点可以取到的范围肯定是逐渐变大,最后变成 [ b , n − 1 ] [b,n-1] [b,n1]
我们尝试掐头去尾的计算这部分的答案。

Part 2.1

我们找到满足 r ≤ b r\le b rb 的最靠左询问串,设其左端点为 u u u,那么当 x ≤ u x\le u xu 时, y y y 的取值范围就变成了 [ b , n − 1 ] [b,n-1] [b,n1]
这部分的贡献是:
( u − 1 ) ( n − b ) (u-1)(n-b) (u1)(nb)
需要注意一些边界情况,如果 u ≥ a u\ge a ua,那么整个 part2 的贡献就是 ( a − 1 ) ( n − b ) (a-1)(n-b) (a1)(nb),加上后直接返回即可。

Part 2.2

l > a l>a l>a 的最靠左的询问串的左端点为 s u f suf suf l ≤ a l\le a la 的最靠右的询问串左端点为 p r e pre pre,那么当 x ∈ ( p r e , a ] x\in(pre,a] x(pre,a] 时, y y y 一直是在受 s u f suf suf 这个串约束,其范围是 [ s u f + l e n − 1 , n − 1 ] [suf+len-1,n-1] [suf+len1,n1]
这部分的贡献就是:
( a − p r e ) ( n − s u f ) (a-pre)(n-suf) (apre)(nsuf)

Part 2.3

现在我们只剩下 x ∈ [ u + 1 , p r e ] x\in[u+1,pre] x[u+1,pre] 这一段的贡献了。

我们想想最理想的情况:每个位置都有一个询问串的左端点,那么答案显然就是:
∑ i = u + 1 p r e n − ( i + l e n − 1 ) = s ( n − ( p r e + l e n − 1 ) , n − ( u + 1 + l e n − 1 ) ) \sum_{i=u+1}^{pre}n-(i+len-1)=s(n-(pre+len-1),n-(u+1+len-1)) i=u+1pren(i+len1)=s(n(pre+len1),n(u+1+len1))
但是现实很骨感,真实答案可能会取不到这么多,那么会少多少呢?
举个栗子,设左端点出现集合为 101001 101001 101001,考虑它和 111111 111111 111111 相比少了多少答案。
不难发现,第一段长度为 1 1 1 0 0 0 串使答案减少了 1 1 1,第二段答案为 2 2 2 0 0 0 串使答案减少了 1 + 2 = 3 1+2=3 1+2=3
一般的,一段长度为 L L L 的极长 0 0 0 串,会使答案减少 s ( 1 , L ) s(1,L) s(1,L)
那么问题就变成求 [ u + 1 , p r e ] [u+1,pre] [u+1,pre] 这部分 0 0 0 串减少的答案之和,利用线段树合并 s t a r t p o s startpos startpos 集合即可轻松维护。
(startpos这个名字是我瞎起的,就是正常SAM的endpos)

然后本题就做完了。
时间复杂度 O ( ( n + m ) log ⁡ n ) O((n+m)\log n) O((n+m)logn)

Code \text{Code} Code

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define ull unsigned long long
#define debug(...) fprintf(stderr,__VA_ARGS__)
#define ok debug("OK\n")
using namespace std;

const int N=4e5+100;
const int C=10;

inline ll read(){
	ll x(0),f(1);char c=getchar();
	while(!isdigit(c)) {if(c=='-')f=-1;c=getchar();}
	while(isdigit(c)) {x=(x<<1)+(x<<3)+c-'0';c=getchar();}
	return x*f;
}

int n,m,k;

int tag[N],mn[N],mx[N];
int pos[N];
int pl[N][20];
vector<int>v[N];

#define calc(x) (1ll*(x)*((x)+1)/2)
struct node{
	int llen,rlen,siz;
	ll sum;
};
node operator + (const node &a,const node &b){
	node o;
	o.siz=a.siz+b.siz;
	o.llen=a.llen==a.siz?a.siz+b.llen:a.llen;
	o.rlen=b.rlen==b.siz?b.siz+a.rlen:b.rlen;
	o.sum=a.sum+b.sum;
	o.sum+=calc(a.rlen+b.llen)-calc(a.rlen)-calc(b.llen);
	return o;
}
inline node emp(int len){
	return (node){len,len,len,calc(len)};
}

struct tree{
	int ls,rs;
	node o;
};
int rt[N],tot;
struct Seg{
	tree tr[N*20];
	#define mid ((l+r)>>1)
	inline void pushup(int k,int l,int r){
		node x=tr[k].ls?tr[tr[k].ls].o:emp(mid-l+1);
		node y=tr[k].rs?tr[tr[k].rs].o:emp(r-mid);
		tr[k].o=x+y;
		return;
	}
	inline int copy(int x){
		tr[++tot]=tr[x];
		return tot;
	}
	void upd(int &k,int l,int r,int p){
		k=copy(k);
		if(l==r){
			tr[k].o=(node){0,0,1,0};
			return;
		}
		if(p<=mid) upd(tr[k].ls,l,mid,p);
		else upd(tr[k].rs,mid+1,r,p);
		pushup(k,l,r);
	}
	int merge(int x,int y,int l,int r){
		if(!x||!y) return x|y;
		int now=copy(x);
		tr[now].ls=merge(tr[now].ls,tr[y].ls,l,mid);
		tr[now].rs=merge(tr[now].rs,tr[y].rs,mid+1,r);
		pushup(now,l,r);
		return now;
	}
	int findsuf(int k,int l,int r,int p){
		if(!k) return 0;
		if(l==r) return (l>=p)?l:0;
		if(p>mid) return findsuf(tr[k].rs,mid+1,r,p);
		else{
			int res=findsuf(tr[k].ls,l,mid,p);
			if(res) return res;
			else return findsuf(tr[k].rs,mid+1,r,p);
		}
	}
	int findpre(int k,int l,int r,int p){
		if(!k) return 0;
		if(l==r) return (l<=p)?l:0;
		if(p<=mid) return findpre(tr[k].ls,l,mid,p);
		else{
			int res=findpre(tr[k].rs,mid+1,r,p);
			if(res) return res;
			else return findpre(tr[k].ls,l,mid,p);
		}
	}
	node query(int k,int l,int r,int x,int y){
		if(!k) return emp(min(r,y)-max(l,x)+1);
		if(x<=l&&r<=y) return tr[k].o;
		if(y<=mid) return query(tr[k].ls,l,mid,x,y);
		else if(x>mid) return query(tr[k].rs,mid+1,r,x,y);
		else return query(tr[k].ls,l,mid,x,y)+query(tr[k].rs,mid+1,r,x,y);
	}
}seg;

void dfs(int x,int fa){
	pl[x][0]=fa;
	for(int k=1;pl[x][k-1];k++) pl[x][k]=pl[pl[x][k-1]][k-1];
	for(int to:v[x]){
		dfs(to,x);
		mn[x]=min(mn[x],mn[to]);
		mx[x]=max(mx[x],mx[to]);
		rt[x]=seg.merge(rt[x],rt[to],1,n);
	}
	if(tag[x]){
		seg.upd(rt[x],1,n,tag[x]);
	}
	return;
}

struct SAM{
	int tr[N][C],fa[N],len[N],tot,lst;
	SAM(){
		tot=lst=1;
	}
	inline void ins(int c,int id){
		c-='0';
		int cur=++tot,p=lst;lst=tot;
		len[cur]=len[p]+1;
		pos[id]=cur;
		tag[cur]=id;mn[cur]=mx[cur]=id;
		for(;p&&!tr[p][c];p=fa[p]) tr[p][c]=cur;
		if(!p) fa[cur]=1;
		else{
			int q=tr[p][c];
			if(len[q]==len[p]+1) fa[cur]=q;
			else{
				int nq=++tot;
				mn[nq]=n+1;mx[nq]=0;
				len[nq]=len[p]+1;fa[nq]=fa[q];
				for(int i=0;i<C;i++) tr[nq][i]=tr[q][i];
				fa[cur]=fa[q]=nq;
				for(;p&&tr[p][c]==q;p=fa[p]) tr[p][c]=nq;
			}
		}
		return;
	}
	void build(){
		for(int i=2;i<=tot;i++) v[fa[i]].push_back(i);
		dfs(1,0);
	}
	int find(int l,int r){
		int x=pos[l],L=r-l+1;
		for(int k=18;k>=0;k--){
			if(len[pl[x][k]]>=L) x=pl[x][k];
		}
		return x;
	}
}sam;

inline ll Sum(ll x,ll y){
	if(x>y) return 0;
	else return (x+y)*(y-x+1)/2;
}
ll work(int l,int r){
	int x=sam.find(l,r),len=r-l+1;
	int a=mn[x]+len-1,b=mx[x];
	ll ans=Sum(1,n-a-1)+Sum(1,b-2);
	int w=(b-1)-(a+1)+1;
	if(w>=1) ans-=calc(w);
	a=min(a,b);
	int pre=seg.findpre(rt[x],1,n,a),suf=seg.findsuf(rt[x],1,n,a+1);
	int u=seg.findpre(rt[x],1,n,b-len+1);
	if(u>=a){
		ans+=1ll*(a-1)*(n-b);
		return ans;
	}
	if(suf){
		ans+=1ll*(a-pre)*(n-(suf+len-1));
	}
	if(u) ans+=1ll*(u-1)*(n-b);
	//calc: [u+1,pre]
	u=max(u,1);
	if(u+1<=pre){
		ans+=Sum(n-(pre+len-1),n-(u+1+len-1));		
		node o=seg.query(rt[x],1,n,u+1,pre);
		ans-=o.sum;
	}
	return ans;
}

char s[N];
signed main(){
#ifndef ONLINE_JUDGE
	freopen("a.in","r",stdin);
	freopen("a.out","w",stdout);
#endif
	n=read();m=read();
	scanf(" %s",s+1);
	for(int i=n;i>=1;i--) sam.ins(s[i],i);
	sam.build();
	for(int i=1;i<=m;i++){
		int l=read(),r=read();
		printf("%lld\n",work(l,r));
	}
	return 0;
}
/*
*/
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
根据引用\[1\],取证2022七省联考的考生将获得中国PostgreSQL分会颁发的《中国PostgreSQL认证考试技术能力证书》和工信部下属中国电子工业标准化协会颁发的《中国电子信息行业专业技术认定证书》两份证书。这两份证书具有较高的认可度和应用价值,可以在招投标、求职、职称晋升、抵税等方面发挥作用。此外,取证学员还可以享受全职业周期、全国覆盖的就业推荐服务。\[1\] 关于直径的计算,根据引用\[2\]和引用\[3\],在树的分裂情况下,最后的直径的长度可能是以下三种情况的最大值: 1. tree1的直径,假设tree1中,原来最长链的一段为root1。 2. tree2的直径,假设tree2中,原来最长链的一段为root2。 3. tree1的某一个点和tree2的某一个点连接的最长链。 在情况3中,最优的连线方式是取tree1直径的一半、tree2直径的一半并连线。如果直径是奇数,则需要向上取整。具体表达为:⌈Max1\[u\]/2⌉ + ⌈Max2\[v\]/2⌉ + 1。\[3\] 综上所述,取证2022七省联考的考生将获得两份证书,并且在计算直径时需要考虑三种情况,其中情况3的最优连线方式是取tree1直径的一半、tree2直径的一半并连线,如果直径是奇数,则需要向上取整。 #### 引用[.reference_title] - *1* [全国联考 | PostgreSQL初、中级认证考试(5.22)通过考生公示](https://blog.csdn.net/weixin_46199817/article/details/117329421)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* *3* [『树直径·树形DP』「四校联考」平衡树](https://blog.csdn.net/Ronaldo7_ZYB/article/details/90488429)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值