[AH2017/HNOI2017]影魔

P3722 [AH2017/HNOI2017]影魔

题解:

法一:

[bzoj4826][HNOI2017]影魔

直接转化成区间内单点的贡献,

分开p1,p2考虑

而min(ai,aj),max(ai,aj)要考虑固定一个点、

对于p1,固定i为较小值。发现,这个j只有L[i]或R[i]满足。

对于p2,差分。找j满足max(j+1~i-1)<max(ai,aj)

固定i为较大值。发现,这个j的范围是[L[i]+1,i-1],或者[i+1,R[i]-1]。

具体处理询问的时候,对边界l,r取min,取max即可。(见上面的博客)

发现列出来的式子,

区间、统计小于某个数的个数/这些数的总和,主席树即可。

细节处理好即可。

代码:

#include<bits/stdc++.h>
#define reg register int
#define il inline
#define numb (ch^'0')
#define mid ((l+r)>>1)
using namespace std;
typedef long long ll;
il void rd(int &x){
    char ch;x=0;bool fl=false;
    while(!isdigit(ch=getchar()))(ch=='-')&&(fl=true);
    for(x=numb;isdigit(ch=getchar());x=x*10+numb);
    (fl==true)&&(x=-x);
}
namespace Miracle{
const int N=200000+5;
int n,m;
int a[N],le[N],ri[N];
int sta[N],top;
struct segmenttree{
    ll sum[N*20],sz[N*20];
    int ls[N*20],rs[N*20];
    int rt[N];
    int cnt;
    void ins(int &x,int y,int l,int r,int c){
        x=++cnt;
        sum[x]=sum[y]+c;sz[x]=sz[y]+1;
        ls[x]=ls[y],rs[x]=rs[y];
        if(l==r)return;
        if(c<=mid) ins(ls[x],ls[y],l,mid,c);
        else ins(rs[x],rs[y],mid+1,r,c);        
    }
    ll ql(int x,int y,int l,int r,int c){//num >= c
        int d=sz[x]-sz[y];
        if(!d) return 0;
        if(l==r) return d*(l>=c);
        if(mid>=c) return sz[rs[x]]-sz[rs[y]]+ql(ls[x],ls[y],l,mid,c);
        else return ql(rs[x],rs[y],mid+1,r,c);
    }
    ll qr(int x,int y,int l,int r,int c){//num <= c
        int d=sz[x]-sz[y];
        if(!d) return 0;
        if(l==r) return d*(r<=c);
        if(mid<c) return sz[ls[x]]-sz[ls[y]]+qr(rs[x],rs[y],mid+1,r,c);
        else return qr(ls[x],ls[y],l,mid,c); 
    }
    ll qmin(int x,int y,int l,int r,int c){
        int d=sz[x]-sz[y];
        if(!d) return 0;
        if(l==r) return d*min(l,c);
        if(mid>=c) return (sz[rs[x]]-sz[rs[y]])*c+qmin(ls[x],ls[y],l,mid,c);
        else return sum[ls[x]]-sum[ls[y]]+qmin(rs[x],rs[y],mid+1,r,c);
    }
    ll qmax(int x,int y,int l,int r,int c){
        int d=sz[x]-sz[y];
        if(!d) return 0;
        if(l==r) return d*max(r,c);
        if(mid<c) return (sz[ls[x]]-sz[ls[y]])*c+qmax(rs[x],rs[y],mid+1,r,c);
        else return sum[rs[x]]-sum[rs[y]]+qmax(ls[x],ls[y],l,mid,c);
    }
}L,R;
int p1,p2;
int main(){
    rd(n);rd(m);rd(p1);rd(p2);
    for(reg i=1;i<=n;++i) rd(a[i]);
    for(reg i=1;i<=n;++i){
        while(top&&a[sta[top]]<a[i]) ri[sta[top--]]=i;
        sta[++top]=i;
    }
    while(top) ri[sta[top--]]=n+1;
    for(reg i=n;i>=1;--i){
        while(top&&a[sta[top]]<a[i]) le[sta[top--]]=i;
        sta[++top]=i;
    }
    while(top) le[sta[top--]]=0;
    for(reg i=1;i<=n;++i){
        ++le[i],++ri[i];//warning!!!
        //cout<<i<<" : "<<le[i]<<" "<<ri[i]<<endl;
        L.ins(L.rt[i],L.rt[i-1],1,n+2,le[i]);
        R.ins(R.rt[i],R.rt[i-1],1,n+2,ri[i]);
    }
    int l,r;
    while(m--){
        rd(l);rd(r);
        ll t1=L.ql(L.rt[r],L.rt[l-1],1,n+2,l+1)+R.qr(R.rt[r],R.rt[l-1],1,n+2,r+1);
        //cout<<" t1 "<<t1<<endl;
        ll t2=R.qmin(R.rt[r],R.rt[l-1],1,n+2,r+2)-L.qmax(L.rt[r],L.rt[l-1],1,n+2,l)-2*(r-l+1);
        printf("%lld\n",t1*p1+(t2-t1)*p2);
    }
    return 0;
}

}
int main(){
    Miracle::main();
    return 0;
}

/*
   Author: *Miracle*
   Date: 2018/12/8 19:27:35
*/

法二:

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

这个还是比较有意思的。虽然不如法一优秀。(离线算法,而且还难写)

还是考虑贡献。

但是这次我们不枚举端点,我们枚举c,也就是[i+1,j-1]的最大值

那么,对于i,考虑i作为这个最大值的能贡献出的数对具体是哪些。(注意,具体是哪些,法一我们只关心个数,但是法二要考虑是哪些位置)

对于p1,发现就贡献一个数对(L[i],R[i])(当然,(i,i+1)不会考虑到,要特殊处理)

对于p2,发现,贡献([L[i]+1,i-1],R[i])以及(L[i],[i+1,R[i]-1])这些数对。

(证明不重不漏的话,每个数对在枚举到中间的最大值的时候都会考虑到)

然后,数对抽象成平面直角坐标系中的一个点

(图片来自上面博客)

满足条件的加入的点就是一些点和线段

发现,询问其实是求位于x属于[l,r],y属于[l,r]的点的个数。

离线差分线段树扫描线即可。

这个转化还是很漂亮的。(以前一直在找把区间点对(l,r)抽象成直角坐标系中点的问题。。)

法一法二的共同思路:

考虑每个点的贡献。

法一枚举点对的端点

法二枚举点对之间最大值的位置。

对于不同的枚举有不同的处理方法。

对于这种区间点对问题,经常是考虑每个点的贡献,

并且在枚举的时候,由于比较灵活,所以可以在加上一些钦定

(常用的是靠右的点,但是这个题的话,就不好考虑相对大小关系了,所以钦定是较小值较大值最大值)

如果像这个题还要询问,那么就考虑把一个点的贡献缩成一个值,然后用支持区间查询的数据结构维护一下。

当然询问离线也是常用的方法。

猜你喜欢

转载自www.cnblogs.com/Miracevin/p/10089239.html