题目链接:https://nanti.jisuanke.com/t/A2206
题意:给你n个数,1<=l<=r<=n,求所有本质不同的a[l...r]的最大值的和
分析:本质不同的子串可想到用后缀数组,所有子串等价于该串的所有后缀的所有前缀,利用后缀数组求本质不同的子串的原理,
n-sa[i]+1为该后缀的前缀个数,即枚举的子串个数,height[i]为第i名和第i-1名的最长公共前缀,第i-1名已经计算了故第i名计算时要减掉。
然后就是求a[i...n]的所有前缀的最大值的和,可利用单调栈+线段树解决。
具体做法就是:从后往前遍历原串,这样修改就不会影响之后的,维护一个单减的栈,每次来一个>=栈顶(top)的元素a[i],就会
对a[i...n]的所有前缀的最大值产生影响,更新受影响的部分的值加上a[i]-top,再查询[i,n]的和即查询了后缀a[i...n]的所有前缀的最大值的和。
Ac code:
#include
using namespace std;
typedef long long ll;
const int maxn=2e5+5;
ll a[maxn];
ll tree[maxn<<2],lazy[maxn<<2];
int n;///n从1开始
int sa[maxn], x[maxn], c[maxn], y[maxn],rk[maxn];///最后x数组就是rk数组
int height[maxn];///sa[i]表示排名为i的后缀的起始下标为sa[i],rk[i]表示起始下标为i的后缀的排名
///h[i]=height[rk[i]]表示S[i...n]和排名在它前一名的lcp
vectorsta;
vectorvv;
///SA部分
inline void SA()
{
int m = n+1;
for (int i = 0; i <= m; i++) c[i] = 0;
for (int i = 1; i <= n; i++) c[x[i]]++;
for (int i = 1; i <= m; i++) c[i] += c[i - 1];
for (int i = n; i; i--) sa[c[x[i]]--] = i;
for (int k = 1, p; k <= n; k <<= 1)
{
p = 0;
for (int i = n; i > n - k; i--) y[++p] = i;
for (int i = 1; i <= n; i++)
if (sa[i] > k) y[++p] = sa[i] - k;
for (int i = 0; i <= m; i++) c[i] = 0;
for (int i = 1; i <= n; i++) c[x[i]]++;
for (int i = 1; i <= m; i++) c[i] += c[i - 1];
for (int i = n; i; i--) sa[c[x[y[i]]]--] = y[i];
p = y[sa[1]] = 1;
for (int i = 2, a, b; i <= n; i++)
{
a = sa[i] + k > n ? -1 : x[sa[i] + k];
b = sa[i - 1] + k > n ? -1 : x[sa[i - 1] + k];
y[sa[i]] = (x[sa[i]] == x[sa[i - 1]]) && (a == b) ? p : ++p;
}
swap(x, y);
m = p;
}
}
inline void getheight()///height[i]表示rank为i和i-1名的两个串的lcp
{
int k=0;
for(int i=1; i<=n; i++) rk[sa[i]]=i;
for(int i=1; i<=n; i++)
{
if(rk[i]==1) continue;
if(k) --k;
int j=sa[rk[i]-1];
while(j+k<=n&&i+k<=n&&a[i+k]==a[j+k]) ++k;
height[rk[i]]=k;
}
}
inline void init()
{
for (int i = 1; i <= n; i++) x[i] =lower_bound(vv.begin(),vv.end(),a[i])-vv.begin()+1;///将a[i]=1e6离散化到n,减少复杂度
SA();
getheight();
}
inline void pushup(int rt)
{
tree[rt]=tree[rt<<1]+tree[rt<<1|1];
}
inline void pushdown(int rt,int l,int r)
{
if(lazy[rt]){
int mid=(l+r)>>1;
tree[rt<<1]+=lazy[rt]*(mid-l+1);///注意,是子区间的长度,顺手写成了(r-l+1)wa了一天
tree[rt<<1|1]+=lazy[rt]*(r-mid);
lazy[rt<<1]+=lazy[rt];
lazy[rt<<1|1]+=lazy[rt];
lazy[rt]=0;
}
}
inline void buildtree(int rt,int l,int r)
{
lazy[rt]=0;
if(l==r){
tree[rt]=a[l];
return;
}
int mid=(l+r)>>1;
pushdown(rt,l,r);
buildtree(rt<<1,l,mid);
buildtree(rt<<1|1,mid+1,r);
pushup(rt);
}
inline void update(int rt,int l,int r,int L,int R,ll val)
{
if(L<=l&&r<=R){
lazy[rt]+=val;
tree[rt]+=1ll*val*(r-l+1);
return;
}
int mid=(l+r)>>1;
pushdown(rt,l,r);
if(mid>=L) update(rt<<1,l,mid,L,R,val);
if(mid>1;
ll ans=0;
pushdown(rt,l,r);
if(mid>=L) ans+=query(rt<<1,l,mid,L,R);
if(mid=1;i--){///利用单调栈+线段树可计算a[i...n]的所有前缀的最大值的和
while(a[i]>=a[sta.back()]){
update(1,1,n,sta[sta.size()-1],sta[sta.size()-2]-1,a[i]-a[sta.back()]);
sta.pop_back();
}
sta.push_back(i);
int l=i;
if(rk[i]!=1)///rk[i]为起始下标为i的后缀排名,height[i]为第i名和i-1名的lcp,所以height[1]没有值
l+=height[rk[i]];///原本应该计算的所有子串为a[i...n]的所有前缀,但第i名的后缀和第i-1名的后缀
///的lcp为height[rk[i]],为去掉重复的子串,应跳过该lcp,和后缀数组求不同子串原理相同
if(l<=n)
ans+=query(1,1,n,l,n);
}
printf("%lld\n",ans);
}
return 0;
}