2018 ICPC 焦作 H题 Can You Solve the Harder Problem?

题意:

给出n个数,定义 f [ l, r ]表示 区间 [ l , r ]的最大值,求所有 子区间的最大值的和,要求相同的子区间只能算一次

比如 数列 5  6 5 6 , 区间 [ 1, 2 ] 和 [ 3, 4]是一模一样的,所以只能算一次。

题解:

假如抛开限制,那就是一个经典的算贡献的题目。

要求重复的区间只能算一次,很容易想到 相同的子串,又联想到后缀数组。

后缀数组中height数组表示字典序相邻的两个后缀的LCP,那么显然LCP就是重复的,我们不能算,如图

对于字典序排第K的后缀,其LCP的部分已经在K-1中计算了,所以我们要新算的区间是右端点在红色圈内的区间

这样就保证不会算重了,那么问题是如何快速计算图中右端点在红色圈内的最大值的和

先将问题泛化

给定n个数,对于每个位置,如何求出以它为左端点的所有区间的最大值的和,这个问题与上图的区别就是计算了LCP的部分,我们一会再想办法解决

对于数 x ,他的贡献只能是 从右端点是它本身的区间开始,向右延伸,直到大于x,因为右端点在向后,区间最大值就大于x了,不能算x的贡献

上图所示的区间才能算x的贡献

这样,方法就显然了,对于每个数,找到右边第一个大于它的位置y,中间的数字的个数乘上x再加上已经求出y的答案,就是x的答案

右边第一个大于它的位置可以用单调栈解决

问题解决了一半,怎么才能不算LCP的部分呢

先罗列出从左端点开始递增的一部分,为什么?因为只有这些数才产生了贡献

上图中,右端点在3号往右的区间的最大值的和,就等于左端点为3号的所有区间的最大值的和,这个我们刚刚解决了

而图中右端点介于红线与3号之间的部分,他们的最大值是2号

所以,我们只需要求出最靠近LCP的点2号与3号,求可以求解问题了

怎么求呢

我们已经知道每个数向右第一个大于本身的数的位置了,一个一个往右跳是显然不行的,我们可以用二分的思想,先跳一大步,如果超过了LCP,那下一次就从原地跳少点,如果没过,我们先跳过去,下次再跳小一点

这样,在log的时间内就找到在LCP附近最近的数的位置了,所有的问题也都解决了

代码:

#include<bits/stdc++.h>
#define N 200010
#define INF 0x3f3f3f3f
#define eps 1e-10
#define pi 3.141592653589793
#define P 998244353
#define LL long long
#define pb push_back
#define cl clear
#define si size
#define lb lower_bound
#define ub upper_bound
#define mem(x) memset(x,0,sizeof x)
#define sc(x) scanf("%d",&x)
#define scc(x,y) scanf("%d%d",&x,&y)
#define sccc(x,y,z) scanf("%d%d%d",&x,&y,&z)
using namespace std;

int a[N],len[N],lg[N];
LL w[N];
int sa[N],t[N],t2[N],c[N],n;
int rak[N],height[N];

void build_sa(int m,int *s)
{
    int i,*x=t,*y=t2;
    for (i=0;i<m;i++)c[i]=0;
    for (i=0;i<n;i++)c[x[i]=s[i]]++;
    for (i=1;i<m;i++)c[i]+=c[i-1];
    for (i=n-1;i>=0;i--)sa[--c[x[i]]]=i;
    for (int k=1;k<=n;k<<=1)
    {
        int p=0;
        for (i=n-k;i<n;i++)y[p++]=i;
        for (i=0;i<n;i++)if (sa[i]>=k)y[p++]=sa[i]-k;
        for (i=0;i<m;i++)c[i]=0;
        for (i=0;i<n;i++)c[x[y[i]]]++;
        for (i=0;i<m;i++)c[i]+=c[i-1];
        for (i=n-1;i>=0;i--) sa[--c[x[y[i]]]]=y[i];
        swap(x,y);
        p=1; x[sa[0]]=0;
        for (i=1;i<n;i++)
            x[sa[i]]=y[sa[i-1]]==y[sa[i]]&&y[sa[i-1]+k]==y[sa[i]+k]?p-1:p++;
        if (p>=n) break;
        m=p;
    }
}

void getheight()
{
    int i,j,k=0;
    for (i=0;i<n;i++)rak[sa[i]]=i;
    for (i=0;i<n;i++)
    {
        if (k)k--;
        j=sa[rak[i]-1];
        while (a[i+k]==a[j+k])k++;
        height[rak[i]]=k;
    }
}

int g[19][N];
int q[N];

int main()
{
    for (int i=1;(1<<i)<N;i++) lg[1<<i]=1;
    for (int i=1;i<N;i++) lg[i]+=lg[i-1];
    int T;
    sc(T);
    while(T--)
    {
        sc(n);
        for (int i=1;i<=n;i++) sc(a[i]),q[i]=a[i];
    	sort(q+1,q+n+1);			//离散化
	    int cnt=unique(q+1,q+n+1)-q-1;
	    for (int i=1;i<=n;i++) a[i]=lb(q+1,q+cnt+1,a[i])-q;
	    a[n+1]=INF;
        stack<int>st; st.push(n+1);

        for (int i=n;i>0;i--)		//单调栈求右边第一个大于的数的位置
        {
        	while(a[i]>=a[st.top()]) st.pop();
        	len[i]=g[0][i]=st.top();
        	st.push(i);
        }

        g[0][n+1]=n+1;
        for (int j=1;(1<<j)<=n+1;j++) 		//向右走2^j步后的位置
            for (int i=1;i<=n+1;i++) g[j][i]=g[j-1][g[j-1][i]];

        w[n]=q[a[n]]; w[n+1]=0;

        for (int i=n-1;i>0;i--) 	//i为左端点的所有区间最大值的和
        	w[i]=(LL)q[a[i]]*(len[i]-i)+w[len[i]];

        for (int i=1;i<=n;i++) a[i-1]=a[i]; a[n++]=0;

        build_sa(cnt+2,a);
    	
        getheight();
    
        LL ans=0;
       
        for (int i=1;i<n;i++)
        {
            int h=height[i];

            if(h==0) {ans+=w[sa[i]+1];continue;}

            int la=sa[i]+h;

            int up=lg[n-sa[i]-1],st=sa[i]+1;

            for (int j=up;j>=0;j--)		//确定在LCP最靠左的位置--st
                if (g[j][st]<=la) st=g[j][st];

            ans+=(LL) q[a[st-1]]*(g[0][st]-la-1)+w[g[0][st]];//两部分相加
        }
        printf("%I64d\n",ans);
    }
    return 0;
}

/*
1
6
1 1 1 2 1 2
*/

猜你喜欢

转载自blog.csdn.net/nudt_spy/article/details/84933870