BZOJ4826:[Hnoi2017]影魔 (单调栈+扫描线+线段树)

题目传送门:http://www.lydsy.com/JudgeOnline/problem.php?id=4826


题目分析:这题是我在今年4月份省赛前看到的,那个时候想了一下,发现不会做。7个月后的今天,我试图把这个坑填了,然而想了很久还是不会做,最后只好%了一波网上的题解,才发现自己智商低下,思维僵化QAQ。

本题的主要思路是转化贡献。由于k是1~n的一个排列,所以如果L+1 < <script type="math/tex" id="MathJax-Element-309"><</script>R,(L,R)中必定有唯一一个最大值。我们不妨枚举这个最大值的位置i,然后找到i左边和右边第一个比i大的地方Left[i],Right[i]。那么当L属于[Left[i],i),R属于(i,Right[i]]的时候,i就是区间(L,R)的最大值。根据题意,点对(Left[i],Right[i])的攻击力为p1,点对(x,Right[i])(Left[i]+1<=x<=i-1)和(Left[i],x)(i+1<=x<=Right[i]-1)的攻击力为p2。其余的点对要么中间的最大值不在i处,要么无攻击力。

如果以L为x轴,R为y轴,就可以将上述的攻击力的计算转化为平面内两条线段和一个点的加法。由于每一个合法点对都会被计算仅一次贡献(点对(i,i+1)除外,由于它中间没有数,要特殊处理),所以攻击力和平面上的点,线段一一对应。而询问区间[a,b]的攻击力,就相当于询问这个平面内的一个正方形的和。于是离线,对询问差分,再横竖扫一遍这个平面,维护一个区间加法的线段树即可:

这题我一开始没有想到将攻击力转化成二维平面上的点和线段,然后想着维护一个单调栈,求出R为某个值的时候,哪些L有贡献,然后就GG了,发现根本维护不了。看了题解之后我还想着(L,R)中有多个最大值怎么办,最后忽然意识到这是个排列……
(好像网上很多dalao都用了可持久化线段树,估计是懒得离线吧qaq)


CODE:

#include<iostream>
#include<string>
#include<cstring>
#include<cmath>
#include<cstdio>
#include<cstdlib>
#include<stdio.h>
#include<algorithm>
using namespace std;

const int maxn=200100;
typedef long long LL;

struct Tnode
{
    LL lazy,sum;
} tree[maxn<<2];

struct data
{
    int id,l,r,cnt;
} ;
data X[maxn<<1];
data Y[maxn];
int numx=0,numy=0;

struct A
{
    int val,num,p;
} ask[maxn<<1];
LL ans[maxn];
int cur=0;

int sak[maxn];
int tail;

int Left[maxn];
int Right[maxn];

int a[maxn];
int aL[maxn];
int aR[maxn];
int n,m,p1,p2;

void Preparation()
{
    tail=0;
    sak[0]=0;
    for (int i=1; i<=n; i++)
    {
        while ( tail && a[ sak[tail] ]<=a[i] ) tail--;
        Left[i]=sak[tail];
        sak[++tail]=i;
    }

    tail=0;
    sak[0]=n+1;
    for (int i=n; i>=1; i--)
    {
        while ( tail && a[ sak[tail] ]<=a[i] ) tail--;
        Right[i]=sak[tail];
        sak[++tail]=i;
    }

    for (int i=1; i<=n; i++)
    {
        if ( Left[i] && Right[i]<=n )
            numx++,X[numx].id=Left[i],X[numx].l=X[numx].r=Right[i],X[numx].cnt=p1;
        if ( Left[i] && Right[i]>i+1 )
            numx++,X[numx].id=Left[i],X[numx].l=i+1,X[numx].r=Right[i]-1,X[numx].cnt=p2;
        if ( Right[i]<=n && Left[i]<i-1 )
            numy++,Y[numy].id=Right[i],Y[numy].l=Left[i]+1,Y[numy].r=i-1,Y[numy].cnt=p2;
    }
}

bool Comp1(A x,A y)
{
    return x.val<y.val;
}

bool Comp2(data x,data y)
{
    return x.id<y.id;
}

void Down(int root,int L,int R)
{
    if (tree[root].lazy)
    {
        int Le=root<<1;
        int Ri=Le|1;
        int mid=(L+R)>>1;
        LL &v=tree[root].lazy;

        tree[Le].lazy+=v;
        tree[Le].sum+=( v*(long long)(mid-L+1) );
        tree[Ri].lazy+=v;
        tree[Ri].sum+=( v*(long long)(R-mid) );
        v=0;
    }
}

void Update(int root,int L,int R,int x,int y,LL v)
{
    if ( y<L || R<x ) return;
    if ( x<=L && R<=y )
    {
        tree[root].lazy+=v;
        tree[root].sum+=( v*(long long)(R-L+1) );
        return;
    }

    Down(root,L,R);

    int Le=root<<1;
    int Ri=Le|1;
    int mid=(L+R)>>1;

    Update(Le,L,mid,x,y,v);
    Update(Ri,mid+1,R,x,y,v);

    tree[root].sum=tree[Le].sum+tree[Ri].sum;
}

LL Query(int root,int L,int R,int x,int y)
{
    if ( y<L || R<x ) return 0;
    if ( x<=L && R<=y ) return tree[root].sum;

    Down(root,L,R);

    int Le=root<<1;
    int Ri=Le|1;
    int mid=(L+R)>>1;

    LL vL=Query(Le,L,mid,x,y);
    LL vR=Query(Ri,mid+1,R,x,y);
    return (vL+vR);
}

int main()
{
    freopen("4826.in","r",stdin);
    freopen("4826.out","w",stdout);

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

    Preparation();

    for (int i=1; i<=m; i++)
    {
        int Le,Ri;
        scanf("%d%d",&Le,&Ri);
        ans[i]=p1*(Ri-Le);
        aL[i]=Le;
        aR[i]=Ri;

        cur++;
        ask[cur].num=i;
        ask[cur].val=Le-1;
        ask[cur].p=-1;

        cur++;
        ask[cur].num=i;
        ask[cur].val=Ri;
        ask[cur].p=1;
    }
    sort(ask+1,ask+cur+1,Comp1);

    sort(X+1,X+numx+1,Comp2);
    int t1=1,t2=1;
    while ( !ask[t1].val && t1<=cur ) t1++;
    for (int i=1; i<=n; i++)
    {
        while ( X[t2].id<=i && t2<=numx )
            Update(1,1,n,X[t2].l,X[t2].r,X[t2].cnt),t2++;
        while ( ask[t1].val<=i && t1<=cur )
        {
            int v=ask[t1].num;
            ans[v]+=( (long long)ask[t1].p*Query(1,1,n,aL[v],aR[v]) );
            t1++;
        }
    }

    for (int i=1; i<=(n<<2); i++) tree[i].lazy=tree[i].sum=0;
    sort(Y+1,Y+numy+1,Comp2);
    t1=1,t2=1;
    while ( !ask[t1].val && t1<=cur ) t1++;
    for (int i=1; i<=n; i++)
    {
        while ( Y[t2].id<=i && t2<=numy )
            Update(1,1,n,Y[t2].l,Y[t2].r,Y[t2].cnt),t2++;
        while ( ask[t1].val<=i && t1<=cur )
        {
            int v=ask[t1].num;
            ans[v]+=( (long long)ask[t1].p*Query(1,1,n,aL[v],aR[v]) );
            t1++;
        }
    }

    for (int i=1; i<=m; i++) printf("%lld\n",ans[i]);
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值