[Hnoi2016]序列 解题报告

我们考虑从左往右扫右端点和从右往左扫左端点的两遍扫描线。(以下选取从左往右的扫描线来说明)考虑每个点向它左边第一个比它大的点连边形成的树。设i左边第一个比它大的点的坐标是 lasti (如果没有则 lasti=0 ),i右边第一个比它大的点的坐标是 nexti (如果没有则 nexti=n+1 )。设[l,r]的最小值的坐标是x,则从r到x的路径上的点y的贡献是 ay(ylasti)(ry+1) (不包括x,x单独算),被[l,r]完全包含的子树y的贡献是 ay(ylasty)(nextyy) ,这两个显然是都可以前缀和出来的。我们只需要用栈维护当前节点的祖先们,然后二分找到最接近左端点的即可。
一个需要注意的地方是,为了保证区间最小值是一样的,从左往右和从右往左时树的形态要是不同的。比如说从左往右扫时,一个点的父亲是它左边第一个小于它的点,那么从右往左扫时一个点的父亲就应该是它右边第一个小于等于它的点。
代码:

#include<cstdio>
#include<iostream>
using namespace std;
#include<algorithm>
#include<cstring>
const int N=1e5+5,Q=1e5+5;
typedef long long LL;
int n;
LL a[N];

void in(int &x){
    char c=getchar();
    bool flag=0;
    for(;c<'0'||c>'9';c=getchar())flag=c=='-';
    for(x=0;c>='0'&&c<='9';c=getchar())x=x*10+(c^'0');
    if(flag)x=-x;
}

void in(LL &x){
    char c=getchar();
    bool flag=0;
    for(;c<'0'||c>'9';c=getchar())flag=c=='-';
    for(x=0;c>='0'&&c<='9';c=getchar())x=x*10+(c^'0');
    if(flag)x=-x;
}

int stack[N],top;
LL s0[N],s1[N],s2[N];

struct QS{
    int l,r,i;
}que[Q];
LL ans[Q];
bool cmp0(const QS &a,const QS &b){
    return a.r<b.r;
}
bool cmp1(const QS &a,const QS &b){
    return a.l>b.l;
}
int main(){
    freopen("bzoj_4450.in","r",stdin);
    freopen("bzoj_4450.out","w",stdout);

    int n,q;
    in(n),in(q);
    for(int i=1;i<=n;++i)in(a[i]);
    for(int i=0;i<q;++i){
        in(que[i].l),in(que[i].r);
        que[i].i=i;
    }

    a[0]=a[n+1]=-0x7fffffff;
    int l,r;
    LL tmp=0;

    sort(que,que+q,cmp0);
    stack[0]=0;
    top=1;
    for(int i=1,j=0;i<=n;++i){
        //printf("----%d----\n",i);
        for(;a[stack[top-1]]>=a[i];--top){
            //printf("Push out %d\n",stack[top-1]);
            tmp+=a[stack[top-1]]*(stack[top-1]-stack[top-2])*(i-stack[top-1]);
        }
        //cout<<tmp<<endl;
        s0[top]=s0[top-1]+a[i]*(i-stack[top-1])*(i-1);
        s1[top]=s1[top-1]+a[i]*(i-stack[top-1]);
        s2[top]=tmp;
        stack[top]=i;
        //for(int k=1;k<=top;++k)printf("%d:%I64d,%I64d,%I64d\n",stack[k],s0[k],s1[k],s2[k]);
        for(;que[j].r==i;++j){
            l=0,r=top;
            while(r-l>1)
                if(stack[l+r>>1]>=que[j].l)r=l+r>>1;
                else l=l+r>>1;

            ans[que[j].i]+=(s2[top]-s2[r])+((s1[top]-s1[r])*i-(s0[top]-s0[r]));

            //printf("Get[%d,%d](%d)=%I64d+%I64d\n",que[j].l,que[j].r,stack[r],(s2[top]-s2[r]),((s1[top]-s1[r])*i-(s0[top]-s0[r])));
        }
        ++top;
    }

    sort(que,que+q,cmp1);
    stack[0]=n+1;
    top=1;
    tmp=0;
    for(int i=n,j=0;i;--i){
        //printf("---%d---\n",i);
        for(;a[stack[top-1]]>a[i];--top)tmp+=a[stack[top-1]]*(stack[top-2]-stack[top-1])*(stack[top-1]-i);
        s0[top]=s0[top-1]+a[i]*(stack[top-1]-i)*(i+1);
        s1[top]=s1[top-1]+a[i]*(stack[top-1]-i);
        s2[top]=tmp;
        stack[top]=i;
        //for(int k=1;k<=top;++k)printf("%d:%I64d,%I64d,%I64d\n",stack[k],s0[k],s1[k],s2[k]);
        for(;que[j].l==i;++j){
            //printf("---%d---\n",j);
            l=0,r=top;

            while(r-l>1){
                //cout<<l<<","<<r<<endl;
                if(stack[l+r>>1]<=que[j].r)r=l+r>>1;
                else l=l+r>>1;
            }

            ans[que[j].i]+=(s2[top]-s2[r])+((s0[top]-s0[r])-(s1[top]-s1[r])*i)+a[stack[r]]*(que[j].r-stack[r]+1)*(stack[r]-que[j].l+1);

            //cout<<s0[top]-s0[l]<<"-"<<s1[top]-s1[l]<<endl;
            //printf("Get[%d,%d](%d)=%I64d+%I64d+%I64d\n",que[j].l,que[j].r,stack[r],(s2[top]-s2[l]),((s0[top]-s0[l])-(s1[top]-s1[l])*i),a[stack[r]]*(que[j].r-stack[r]+1)*(stack[r]-que[j].l+1));
        }
        ++top;
    }

    for(int i=0;i<q;++i)printf("%I64d\n",ans[i]);
}

总结:
①大数据拍不出错,小数据说不定一拍就错!
②笛卡尔树的题目中相等情况一定要考虑清楚。

你可能感兴趣的:(栈,分治,扫描线)