[Hnoi2016]序列 解题报告

4 篇文章 0 订阅
3 篇文章 0 订阅

我们考虑从左往右扫右端点和从右往左扫左端点的两遍扫描线。(以下选取从左往右的扫描线来说明)考虑每个点向它左边第一个比它大的点连边形成的树。设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]);
}

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值