zoukankan      html  css  js  c++  java
  • [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
    ",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)抽象成直角坐标系中点的问题。。)

    法一法二的共同思路:

    考虑每个点的贡献。

    法一枚举点对的端点

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

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

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

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

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

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

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

  • 相关阅读:
    nowcoderD Xieldy And His Password
    Codeforces681D Gifts by the List
    nowcoder80D applese的生日
    Codeforces961E Tufurama
    Codeforces957 Mahmoud and Ehab and yet another xor task
    nowcoder82E 无向图中的最短距离
    nowcoder82B 区间的连续段
    Codeforces903E Swapping Characters
    Codeforces614C Peter and Snow Blower
    Codeforces614D Skills
  • 原文地址:https://www.cnblogs.com/Miracevin/p/10089239.html
Copyright © 2011-2022 走看看