2018-2019 ACM-ICPC, Asia Jiaozuo Regional Contest H题(后缀数组+单调栈+线段树)

题目链接:https://nanti.jisuanke.com/t/A2206

题意:给你n个数,1<=l<=r<=n,求所有本质不同的a[l...r]的最大值的和

分析:本质不同的子串可想到用后缀数组,所有子串等价于该串的所有后缀的所有前缀,利用后缀数组求本质不同的子串的原理,

即本质不同的子串=总子串个数-本质相同的子串个数=\sum_{i=1}^{n}(n-sa[i]+1-height[i])

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;
}

 

你可能感兴趣的:(数据结构,比赛补题)