BZOJ 4826: [AH2017/HNOI2017]影魔 BIT 扫描线

title

BZOJ 4826

LUOGU 3722

简化题意:

一个长度为 \(n\) 的序列,如果对于两个位置 \(l,r\),他们是区间 \([l,r]\) 中的最大值和次大值,产生 \(p_1\) 的贡献;如果恰好有一个是最大值,产生 \(p_2\) 的贡献。

analysis

让我想起了冯·霍恩海姆。

看了半天题面,尝试自己概括题意,抽出模型,然而...

这题面真的不是人话,所以我就去看了 yyb\(blog\) ,这才看懂了题意。

发现这两个贡献的条件是有重叠的,即这个区间的一个端点一定是这个区间的最大值,那就只有先把这个处理出来,才能做下面的工作。

即处理出每个点 \(i\) 左边第一个比它大的点 \(L[i]\) ,和右边第一个比它大的点 \(R[i]\) ,这个可以用一个单调栈扫两遍就好了。

那么

  1. 那么对于区间 \(L[i]\)\(R[i]\)\(p_1\) 的贡献;
  2. 对于左端点在 \(L[i]+1\sim i-1\) ,右端点为 \(R[i]\) 的区间有 \(p_2\) 的贡献;
  3. 对于左端点为 \(L[i]\) ,右端点为 \(i+1\sim R[i]-1\) 的区间也有 \(p_2\) 的贡献。

所以可以离线处理一下。

然后

  1. 我们在扫到 \(R[i]\) 时,更新点 \(L[i]\) 的贡献;
  2. 我们在扫到 \(R[i]\) 时,更新区间 \(L[i]+1\sim i-1\) 的贡献;
  3. 我们在扫到 \(L[i]\) 时,更新区间 \(i+1\sim R[i]-1\) 的贡献。

这样就与处理好所有区间的贡献了。

那么对于每个询问 \([l,r]\) ,把上面的三种情况全部抽象成二维平面上的线段1,那么询问就相当于求这个区间内的权值和,用扫描线和区间修改 \(BIT\) 可以完成这个操作。

code

#include<bits/stdc++.h>

typedef long long ll;
const int maxn=2e5+10;

namespace IO
{
    char buf[1<<15],*fs,*ft;
    inline char getc() { return (ft==fs&&(ft=(fs=buf)+fread(buf,1,1<<15,stdin),ft==fs))?0:*fs++; }
    template<typename T>inline void read(T &x)
    {
        x=0;
        T f=1, ch=getchar();
        while (!isdigit(ch) && ch^'-') ch=getchar();
        if (ch=='-') f=-1, ch=getchar();
        while (isdigit(ch)) x=(x<<1)+(x<<3)+(ch^48), ch=getchar();
        x*=f;
    }

    char Out[1<<24],*fe=Out;
    inline void flush() { fwrite(Out,1,fe-Out,stdout); fe=Out; }
    template<typename T>inline void write(T x,char str)
    {
        if (!x) *fe++=48;
        if (x<0) *fe++='-', x=-x;
        T num=0, ch[20];
        while (x) ch[++num]=x%10+48, x/=10;
        while (num) *fe++=ch[num--];
        *fe++=str;
    }
}

using IO::read;
using IO::write;

int n,m,p1,p2;
namespace BIT
{
    ll c1[maxn],c2[maxn];
    inline int lowbit(int x) { return x & -x; }
    inline void add(int x,int v) { for (int i=x; i<=n; i+=lowbit(i)) c1[i]+=v, c2[i]+=x*v; }
    inline ll ask(int x) { ll ans=0; for (int i=x; i; i-=lowbit(i)) ans+=(x+1)*c1[i]-c2[i]; return ans; }
    inline ll hsh(int l,int r) { return ask(r)-ask(l-1); }
}

using BIT::add;
using BIT::hsh;

struct QwQ{int x,l,r,v,id;}q[maxn<<2];
int cnt;
inline bool cmp1(QwQ a,QwQ b) { return a.x<b.x; }

struct Orz{int x,l,r,v;}o[maxn<<2];
int tot;
inline bool cmp2(Orz a,Orz b) { return a.x<b.x; }

ll ans[maxn]; int L[maxn],R[maxn];
int k[maxn],Stack[maxn],top;
int main()
{
    read(n);read(m);read(p1);read(p2);
    for (int i=1; i<=n; ++i) read(k[i]);
    Stack[top=1]=0;
    for (int i=1; i<=n; ++i)
    {
        while (top>1 && k[Stack[top]]<k[i]) --top;
        L[i]=Stack[top], Stack[++top]=i;
    }
    Stack[top=1]=n+1;
    for (int i=n; i>=1; --i)
    {
        while (top>1 && k[Stack[top]]<k[i]) --top;
        R[i]=Stack[top], Stack[++top]=i;
    }
    for (int i=1,l,r; i<=m; ++i)
    {
        read(l);read(r);
        ans[i]=(r-l)*p1;
        q[++cnt]=(QwQ){r,l,r,1,i};
        q[++cnt]=(QwQ){l-1,l,r,-1,i};
    }
    std::sort(q+1,q+cnt+1,cmp1);
    for (int i=1; i<=n; ++i)
    {
        if (L[i] && R[i]<n+1) o[++tot]=(Orz){R[i],L[i],L[i],p1};
        if (L[i] && R[i]>i+1) o[++tot]=(Orz){L[i],i+1,R[i]-1,p2};
        if (L[i]+1<i && R[i]<n+1) o[++tot]=(Orz){R[i],L[i]+1,i-1,p2};
    }
    std::sort(o+1,o+tot+1,cmp2);
    for (int i=1, j=1; i<=cnt; ++i)
    {
        while (j<=tot && o[j].x<=q[i].x) add(o[j].l,o[j].v), add(o[j].r+1,-o[j].v), ++j;
        ans[q[i].id]+=q[i].v*hsh(q[i].l,q[i].r);
    }
    for (int i=1; i<=m; ++i) write(ans[i],'\n');
    IO::flush();
    return 0;
}

  1. 这个抽象成二维平面上的线段的思想,也就是使用扫描线的原因,细看 QYP_2002\(blog\)

猜你喜欢

转载自www.cnblogs.com/G-hsm/p/11449311.html