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)抽象成直角坐标系中点的问题。。)

    法一法二的共同思路:

    考虑每个点的贡献。

    法一枚举点对的端点

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

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

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

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

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

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

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

  • 相关阅读:
    java连接oracle数据库的实现代码
    java.sql.SQLException: Io 异常: Connection refused(DESCRIPTION=(TMP=)(VSNNUM=186646784)(ERR=12505)(ERR
    Java连接MySQl数据库实现代码
    在JSP中使用BootStrap
    在Eclipse中添加添加一些有助于开发的插件
    再eclipse的javaweb项目中添加JQuery文件时jquery-2.1.4.min.js报错
    Oracle中的触发器
    oracle----约束
    oracle----删除数据
    oracle----修改表中的数据
  • 原文地址:https://www.cnblogs.com/Miracevin/p/10089239.html
Copyright © 2011-2022 走看看