P4248-后缀数组,单调栈

本文详细解析了P4248题目的解题思路,通过维护两部分答案,利用单调栈求解每个区间的height最小值之和,实现了高效求解字符串区间最长公共前缀(LCP)的总和。代码中使用了SAC算法进行字符串处理。

P4248

题目描述

题目描述

题解

考虑维护两部分答案。
第一部分是 ∑ 1 ≤ i < j ≤ n l e n [ T i ] + l e n [ T j ] = n ( n − 1 ) ( n + 1 ) / 2 \sum_{1\le i<j\le n}len[T_i]+len[T_j]=n(n-1)(n+1)/2 1i<jnlen[Ti]+len[Tj]=n(n1)(n+1)/2
第二部分是 ∑ 1 ≤ i < j ≤ n 2 ∗ l c p ( T i , T j ) \sum_{1\le i<j\le n}2*lcp(T_i,T_j) 1i<jn2lcp(Ti,Tj),即求每个区间的 h e i g h t height height最小值之和,单调栈即可解决

代码

#include<bits/stdc++.h>
#define M 500009 
#define int long long
using namespace std;
int m,n,rk[M],tp[M],sa[M],tax[M],height[M],ans,q[M],num[M];
char s[M];
int getans(){
	int l=1,r=0,cnt=n;
	for(int i=1;i<=n;i++) num[i]=(n-1)*i;
	ans-=num[cnt--];
	height[n+1]=0;//注意加一个0,清空单调栈 
	for(int i=1;i<=n+1;i++){
		while(l<=r&&height[i]<=height[q[r]]){
			ans+=2*((i-q[r])*(q[r]-q[r-1]))*height[q[r]];
			while(ans>0) ans-=num[cnt--];
			r--;
		}q[++r]=i;
	}while(cnt) ans-=num[cnt--];
	return -ans;
}
void getheight(){
	int j=0,k=0;
	for(int i=1;i<=n;i++){
		if(k) k--;
		int j=sa[rk[i]-1];
		while(s[j+k]==s[i+k]) k++;
		height[rk[i]]=k;
	}
} 
void Qsort(){
	for(int i=0;i<=m;i++) tax[i]=0;
	for(int i=1;i<=n;i++) tax[rk[i]]++;
	for(int i=1;i<=m;i++) tax[i]+=tax[i-1];
	for(int i=n;i>=1;i--) sa[tax[rk[tp[i]]]--]=tp[i];
}
void Suffix(){
	for(int i=1;i<=n;i++) rk[i]=s[i],tp[i]=i;
	m=127;Qsort();
	for(int p=0,w=1;p<n;m=p,w<<=1){
		p=0;
		for(int i=1;i<=w;i++) tp[++p]=n-w+i;
		for(int i=1;i<=n;i++) if(sa[i]>w) tp[++p]=sa[i]-w;
		Qsort();std::swap(tp,rk);
		rk[sa[1]]=p=1;
		for(int i=2;i<=n;i++)
			rk[sa[i]]=(tp[sa[i]]==tp[sa[i-1]]&&tp[sa[i]+w]==tp[sa[i-1]+w])?p:++p;
	}getheight();
	//for(int i=1;i<=n;i++) printf("%lld\n",height[i]);
}
signed main(){
	scanf("%s",s+1);
	n=strlen(s+1);Suffix();
	printf("%lld\n",getans());
	return 0;
}
# CF2126G2 Big Wins! (hard version) ## 题目描述 这是该问题的困难版本。不同之处在于本版本中 $a_i \leq n$。 给定一个长度为 $n$ 的整数数组 $a_1, a_2, \dots, a_n$。 你的任务是找到一个子数组 $a[l, r]$(即一段连续的元素 $a_l, a_{l + 1}, \dots, a_r$),使得表达式 $\text{med}(a[l, r]) - \min(a[l, r])$ 的值最大。 其中: - $\text{med}$ 表示子数组的中位数,即将子数组排序后,第 $\left\lceil \frac{k + 1}{2} \right\rceil$ 个元素,$k$ 为子数组长度; - $\min$ 表示该子数组中的最小元素。 例如,考虑数组 $a=[1, 4, 1, 5, 3, 3]$,选择子数组 $a[2, 5] = [4, 1, 5, 3]$。排序后为 $[1, 3, 4, 5]$。 - $\text{med}(a[2, 5]) = 4$,因为 $\left\lceil \frac{4 + 1}{2} \right\rceil = 3$,排序后第 3 个元素为 $4$; - $\min(a[2, 5]) = 1$,因为最小元素为 $1$。 在本例中,$\text{med} - \min = 4 - 1 = 3$。 ## 输入格式 第一行包含一个整数 $t$($1 \le t \le 10^4$),表示测试用例的数量。 每个测试用例的第一行包含一个整数 $n$($1 \leq n \leq 2 \cdot 10^5$),表示数组的长度。 每个测试用例的第二行包含 $n$ 个整数 $a_1, a_2, \dots, a_n$($1 \leq a_i \leq n$),表示数组的元素。 保证所有测试用例中 $n$ 的总和不超过 $2 \cdot 10^5$。 ## 输出格式 对于每个测试用例,输出一个整数,表示所有子数组中 $\text{med} - \min$ 的最大可能值。 ## 输入输出样例 #1 ### 输入 #1 ``` 5 5 3 2 5 3 1 4 4 1 1 3 7 6 1 3 4 6 2 7 4 4 2 3 1 5 1 2 3 4 5 ``` ### 输出 #1 ``` 3 3 5 2 2 ``` ## 说明/提示 在第一个示例中,考虑数组 $a=[3,\ 2,\ 5,\ 3,\ 1]$,可以选择子数组 $a[2,\ 3]$,即元素 $[2,\ 5]$。 -数组长度为 $2$。 - 中位数为排序后第 $\left\lceil \dfrac{3}{2} \right\rceil = 2$ 个元素。排序后为 $[2,\ 5]$,$\text{med} = 5$。 -数组的最小元素为 $2$。 因此,$\text{med} - \min = 5 - 2 = 3$,这是最大答案。 在第二个测试用例中,数组 $a=[4,\ 1,\ 1,\ 3]$,可以选择子数组 $a[1,\ 2]$,即元素 $[4,\ 1]$。 -数组长度为 $2$。 - 中位数为排序后第 $\left\lceil \dfrac{3}{2} \right\rceil = 2$ 个元素。排序后为 $[1,\ 4]$,$\text{med} = 4$。 -数组的最小元素为 $1$。 因此,$\text{med} - \min = 4 - 1 = 3$。 可以证明,这两个子数组都是最优的,能够得到表达式 $\text{med} - \min$ 的最大值。 由 ChatGPT 4.1 翻译 //https://blog.youkuaiyun.com/ez_gsn/article/details/124840464?ops_request_misc=elastic_search_misc&request_id=743433b74cd2da2925edc4e1cc7af925&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~ElasticSearch~search_v2-1-124840464-null-null.142^v102^pc_search_result_base9&utm_term=P2839%20%5B%E5%9B%BD%E5%AE%B6%E9%9B%86%E8%AE%AD%E9%98%9F%5D%20middle #include<bits/stdc++.h> #define int long long using namespace std; int n; int a[20005]; int b[20005]; int sl; vector<int> pos[20005]; int cnt; int rt[20005]; struct TREE { int l,r; int sum;//区间和 int lml,rml;//最大前缀和,最大后缀和 } tr[20005*40]; void pushup(int x) { tr[x].sum=tr[tr[x].l].sum+tr[tr[x].r].sum; tr[x].lml=max(tr[tr[x].l].lml,tr[tr[x].l].sum+tr[tr[x].r].lml); tr[x].rml=max(tr[tr[x].r].rml,tr[tr[x].r].sum+tr[tr[x].l].rml); } int build(int i,int l,int r) { i=++cnt; if(l==r) { tr[i].sum=tr[i].lml=tr[i].rml=1; //初始线段树对应最小中位数候选值(mid = num[1]), //此时所有元素都 ≥ mid,因此每个位置都是 1。 return i; } int mid=(l+r)/2; tr[i].l=build(tr[i].l,l,mid); tr[i].r=build(tr[i].r,mid+1,r); pushup(i); return i; } int update(int x,int y,int ps,int l,int r) { //单点修改 //f:是否需要新建节点 0:需要 1:不需要 //x:当前新版本的节点索引(需修改的节点) //y:旧版本的对应节点索引(数据来源) x=++cnt; if(l==r) { tr[x].lml=tr[x].rml=tr[x].sum=1; return x; } tr[x]=tr[y]; int mid=(l+r)/2; if(ps<=mid) tr[x].l=update(tr[x].l,tr[y].l,ps,l,mid); else tr[x].r=update(tr[x].r,tr[y].r,ps,mid+1,r); pushup(x); return x; } int query_sum(int i,int l,int r,int qry_l,int qry_r) { //区间和 if(i==0) return 0; if(l>=qry_l&&r<=qry_r) return tr[i].sum; int ans=0,mid=(l+r)/2; if(mid>=qry_l) ans+=query_sum(tr[i].l,l,mid,qry_l,qry_r); if(mid<qry_r) ans+=query_sum(tr[i].r,mid+1,r,qry_l,qry_r); return ans; } TREE query_lml(int i,int l,int r,int qry_l,int qry_r) { //在区间 [qry_l, qry_r] 内部,从 qry_l 开始计算的最大前缀和 if(i==0) return {0,0,0,0,0}; if(l>=qry_l&&r<=qry_r) return tr[i]; int mid=(l+r)/2; if(qry_l<=mid&&qry_r>mid) { //qry_r>r 不能去等,保证右子树不为空 TREE ls,rs; ls=query_lml(tr[i].l,l,mid,qry_l,qry_r); rs=query_lml(tr[i].r,mid+1,r,qry_l,qry_r); TREE ans; ans.sum=ls.sum+rs.sum; ans.lml=max(ls.sum+rs.lml,ls.lml); return ans; } if(qry_l<mid) return query_lml(tr[i].l,l,mid,qry_l,qry_r); else return query_lml(tr[i].r,mid+1,r,qry_l,qry_r); } TREE query_rml(int i,int l,int r,int qry_l,int qry_r) { //在区间 [qry_l, qry_r] 内部,从 qry_l 开始计算的最大前缀和 if(i==0) return {0,0,0,0,0}; if(l>=qry_l&&r<=qry_r) return tr[i]; int mid=(l+r)/2; if(qry_l<=mid&&qry_r>mid) { //qry_r>r 不能去等,保证右子树不为空 TREE ls,rs; ls=query_rml(tr[i].l,l,mid,qry_l,qry_r); rs=query_rml(tr[i].r,mid+1,r,qry_l,qry_r); TREE ans; ans.sum=ls.sum+rs.sum; ans.rml=max(rs.sum+ls.rml,rs.rml); return ans; } if(qry_l<mid) return query_rml(tr[i].l,l,mid,qry_l,qry_r); else return query_rml(tr[i].r,mid+1,r,qry_l,qry_r); } bool cmp(int x,int y) { return a[x]>a[y]; } stack<int> s; int f[20005];//表示位置 i 左边第一个小于 a[i] 的元素的位置 int g[20005];//表示位置 i 右边第一个小于 a[i] 的元素的位置。 main() { // ios::sync_with_stdio(0); // cin.tie(0); // cout.tie(0); int T; cin>>T; while(T--) { int n; cin>>n; for(int i=1; i<=n; i++) cin>>a[i],b[i]=i; sort(b+1,b+1+n,cmp); rt[0]=build(rt[0],1,n); for(int i=1; i<=n; i++) rt[i]=update(rt[i],rt[i-1],b[i],1,n); while(!s.empty()) s.pop(); for (int i = 1; i <= n; i++) f[i] = 0, g[i] = n + 1; for (int i = 1; i <= n; i++) { while (s.size() && a[i] < a[s.top()]) g[s.top()] = i, s.pop(); if (s.size()) f[i] = s.top(); s.push(i); } int ans = 0; for (int i = 1; i <= n; i++) { int x = f[i] + 1, y = g[i] - 1; int l = 1, r = n; while (l < r) { int mid = (l + r) >> 1; int X = query_rml(rt[mid], 1, n, x, i).rml; int Y = -query_sum(rt[mid], 1, n, i, i); int Z = query_lml(rt[mid], 1, n, i, y).lml; if (X + Y + Z >= 0) r = mid; else l = mid + 1; } ans = max(ans, a[b[l]] - a[i]); } cout << ans << '\n'; } } 代码问题
08-26
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值