【BZOJ4826】影魔(AHOI&HNOI2017)-线段树+离线

测试地址:影魔
做法:本题需要用到线段树+离线。
首先你需要注意到,题目中所给的序列是一个全排列(没看到这个条件的我直接跪了)。然后我们转化题目中的限制条件,我们发现这个条件等价于:当ki,kj分别为[i,j]的最大和次大值时,有p1的贡献;当ki,kj中有一个是[i,j]的最大值,而另一个不是次大值时,有p2的贡献。
对于点对的贡献,我们考虑两种情况:
一:贡献为p1的情况。注意到此时,左端点和右端点之间存在一个最大的ks(l<s<r),那么kl,kr应该分别是ks左右距离它最近的比它大的元素。
二:贡献为p2的情况。注意到此时,左端点和右端点之间存在一个最大的ks(l<s<r),分类讨论,如果kl>kr,那么kl应是ks向左距离它最近的比它大的元素,又因为kskr之间没有比ks大的数,也就是说krks向右距离它最近的比它大的元素左边,而kl<kr可以同理分析。
于是我们发现了一种新的统计贡献的方式:枚举ks,找到它向左和向右距离它最近的比它大的元素(用单调栈或者O(nlogn)的数据结构都可以),按照上面的分析统计贡献。注意到上面的贡献都是一个端点固定,另一个端点在一个连续区间中移动的情况,所以我们可以把这些贡献看成固定点对移动点的贡献。又想到一个区间[l,r]中所有点对的总贡献,等于区间[1,r]中的点对该区间的总贡献,减去区间[1,l1]中的点对该区间的总贡献,所以我们离线,再配合线段树维护即可。于是我们就解决了这一题,时间复杂度为O(nlogn)
以下是本人代码:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
int n,m,a[200010],l[200010],r[200010],st[200010],top,tot;
int ql[200010],qr[200010];
ll p1,p2,seg[800010]={0},tag[800010]={0},ans[200010];
bool vis[200010]={0};
struct oper
{
    int pos,l,r;
    ll c;
}p[600010];
struct event
{
    int id,pos;
}q[400010];
bool cmp(oper a,oper b) {return a.pos<b.pos;}
bool cmpe(event a,event b) {return a.pos<b.pos;}

void insert(int pos,int l,int r,ll c)
{
    p[++tot].pos=pos;
    p[tot].l=l,p[tot].r=r;
    p[tot].c=c;
}

void init()
{
    scanf("%d%d%lld%lld",&n,&m,&p1,&p2);
    for(int i=1;i<=n;i++)
        scanf("%d",&a[i]);

    top=0;
    for(int i=1;i<=n;i++)
    {
        while(top&&a[st[top]]<a[i])
        {
            r[st[top]]=i;
            top--;
        }
        if (top) l[i]=st[top];
        else l[i]=0;
        st[++top]=i;
    }
    while(top)
    {
        r[st[top]]=n+1;
        top--;
    }

    tot=0;
    for(int i=1;i<=n;i++)
    {
        if (l[i]!=0&&r[i]!=n+1&&l[i]!=r[i]) insert(l[i],r[i],r[i],p1);
        if (l[i]!=0&&i+1<=r[i]-1) insert(l[i],i+1,r[i]-1,p2);
        if (r[i]!=n+1&&l[i]+1<=i-1) insert(r[i],l[i]+1,i-1,p2);
    }
    sort(p+1,p+tot+1,cmp);
}

void pushdown(int no,ll l,ll r)
{
    if (tag[no]!=0)
    {
        ll mid=(l+r)>>1;
        tag[no<<1]+=tag[no],tag[no<<1|1]+=tag[no];
        seg[no<<1]+=(mid-l+1)*tag[no];
        seg[no<<1|1]+=(r-mid)*tag[no];
        tag[no]=0;
    }
}

void pushup(int no)
{
    seg[no]=seg[no<<1]+seg[no<<1|1];
}

void add(int no,ll l,ll r,int s,int t,ll c)
{
    if (l>=s&&r<=t)
    {
        tag[no]+=c;
        seg[no]+=(r-l+1)*c;
        return;
    }
    ll mid=(l+r)>>1;
    pushdown(no,l,r);
    if (s<=mid) add(no<<1,l,mid,s,t,c);
    if (t>mid) add(no<<1|1,mid+1,r,s,t,c);
    pushup(no);
}

ll query(int no,ll l,ll r,int s,int t)
{
    if (l>=s&&r<=t) return seg[no];
    ll sum=0,mid=(l+r)>>1;
    pushdown(no,l,r);
    if (s<=mid) sum+=query(no<<1,l,mid,s,t);
    if (t>mid) sum+=query(no<<1|1,mid+1,r,s,t);
    return sum;
}

void work()
{
    for(int i=1;i<=m;i++)
    {
        int x=(i<<1)-1,y=(i<<1);
        scanf("%d%d",&ql[i],&qr[i]);
        q[x].pos=ql[i]-1,q[y].pos=qr[i];
        q[x].id=q[y].id=i;
    }
    sort(q+1,q+(m<<1)+1,cmpe);

    int now=1,nowa=1;
    for(int i=1;i<=(m<<1);i++)
    {
        while(nowa<=q[i].pos) add(1,1,n,nowa+1,nowa+1,p1),nowa++;
        while(now<=tot&&p[now].pos<=q[i].pos)
        {
            add(1,1,n,p[now].l,p[now].r,p[now].c);
            now++;
        }
        if (!vis[q[i].id])
        {
            ans[q[i].id]=query(1,1,n,ql[q[i].id],qr[q[i].id]);
            vis[q[i].id]=1;
        }
        else ans[q[i].id]=query(1,1,n,ql[q[i].id],qr[q[i].id])-ans[q[i].id];
    }

    for(int i=1;i<=m;i++)
        printf("%lld\n",ans[i]);
}

int main()
{
    init();
    work();

    return 0;
}
阅读更多
版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/Maxwei_wzj/article/details/80319326
个人分类: 数据结构-线段树
想对作者说点什么? 我来说一句
相关热词

没有更多推荐了,返回首页

关闭
关闭
关闭