• 【转】洛谷 P3722 [AH2017/HNOI2017]影魔 题解


    —— chino在我身下受

    感觉还是挺有思维难度的一道题。蒟蒻想了不短时间才从暴力 O(n3)O(n^3) 优化到 O(nlogn)O(nlogn)

    纯暴力

    O(n3)O(n^3) 暴力枚举区间内的点对,暴力扫描区间最大值。

    稍微好点的暴力

    最大值可以用线段树维护,优化到 O(n2logn)O(n^2logn)

    (其实 stst 表能达到 O(n2)O(n^2) ,不过蒟蒻早忘了咋写了。。。总之最大值可以用数据结构优化)

    (重点!)高端的优化

    似乎到上面就不是很好想了。瓶颈就在于枚举点对的 n2n^2 。可不可以少枚举点东西?

    一个点对 (i,j)(i,j) 的贡献与三个值有关: a[i],a[j],max(a[k])(i<k<j)a[i],a[j],max(a[k])(i<k<j)

    不枚举 i,ji,j ,那就枚举中间的 a[k]a[k] 。这里引入两个值:定义 ll[i]ll[i]从点 ii 向左走,直到遇到第一个大于它的数的位置(没有则为0);同理, rr[i]rr[i] 就是向右走(没有则为n+1)。

    也就是说,ii 是区间 [ll[i]+1,rr[i]1][ll[i]+1,rr[i]-1] 的最大值,且满足这个区间长度最小

    这样,枚举这个点(记为 ii )的时候:(区间记为 [L,R][L,R]

    • 如果 ll[i]ll[i]rr[i]rr[i] 都在区间中,就会有一点对 (ll[i],rr[i])(ll[i],rr[i]) 满足条件 11
    • 如果 ll[i]ll[i] 在区间中,那么每个点对 (ll[i],k)(i<k<min(rr[i],R))(ll[i],k)(i<k<min(rr[i],R)) 满足条件 22 。(因为 a[k]<a[i]a[k]<a[i]a[i]<a[ll[i]]a[i]<a[ll[i]]a[i]a[i] 为区间 [ll[i]+1,k1][ll[i]+1,k-1] 的最大值。注意不能走出去所以取min)
    • 如果 rr[i]rr[i] 在区间中,那么每个点对 (k,rr[i])(max(ll[i],L)<k<i)(k,rr[i])(max(ll[i],L)<k<i) 满足条件 22 。(原因同上)

    这样就能得到 O(n2)O(n^2) 的做法。

    顶级的优化

    前面铺垫这么多,终于轮到正解出场了!

    根据上面,我们需要一种方法,能快速得知:

    • 每个 ll[i]ll[i] 在区间内的数的 min(rr[i],R)imin(rr[i],R)-i
    • 每个 rr[i]rr[i] 在区间内的数的 imax(ll[i],L)i-max(ll[i],L)
    • ll[i]ll[i]rr[i]rr[i] 都在区间内数的个数。

    虽然有点麻烦,不过本题只有询问,是不是可以离线下来搞一搞呢?

    先处理第一种。从右往左扫描数列,遇到某个数的 ll[i]ll[i] 再把这个数产生的贡献用某种数据结构维护起来,遇到某个询问的左端点就直接查询,这样保证了只有 ll[i]ll[i] 在区间内的数才会产生贡献

    具体来说:用线段树维护。遇到 ll[i]ll[i] 就把 [i+1,rr[i]1][i+1,rr[i]-1] (注意不能取两端)加 11 ,遇到询问左端点就查询 [L,R][L,R] 的和。仔细想想就会发现,这样也会在查询时相当于取了 min(rr[i],R)min(rr[i],R)

    第二种倒过来扫就好了。

    第三种正着扫倒着扫皆可。以倒着扫为例:遇到 ll[i]ll[i][rr[i],n][rr[i],n]11 (意为询问右端点落在 [rr[i],n][rr[i],n] 都会覆盖到 rr[i]rr[i] ,就有 1 贡献),这样查询时单点查 RR 即可。

    时间复杂度: O(nlogn)O(nlogn) (常数有点大最慢 1100ms+1100ms+ 。。。)

    细节:1.关于预处理 llllrrrr :蒟蒻好像想复杂了,没有什么好方法,只能记录下位置,按权值排序后造棵平衡树倒着扫数列,插入每个点的位置, llllrrrr 分别查前驱后继。。。

    2.统计的答案要开long long!

    update:update:

    3.有一点忘了说了:对于情况1,满足 i+1=ji+1=j 同样会有贡献,而该方法统计不上,所以输出时直接加上就好。

    上代码:

    #define Kafuu signed
    #define Chino main
    

    #include <iostream>
    #include <cstdio>
    #include <algorithm>
    #include <cmath>
    #include <cstring>
    #include <vector>

    #define maxn 200005
    #define inf 0x3f3f3f

    using namespace std;

    inline int read(){
    int x=0,y=0;
    char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch'-')y=1;ch=getchar();}
    while(ch>='0'&&ch<='9')x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
    return y?-x:x;
    }
    template<typename T>
    inline T read(){
    T x=0;
    int y=0;
    char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch
    '-')y=1;ch=getchar();}
    while(ch>='0'&&ch<='9')x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
    return y?-x:x;
    }//快读
    inline int ran(){
    static int seed=45562;
    return seed=seed48271LL%2147483647;
    }//一个优化常数的随机数函数
    int a[maxn],pos[maxn],ll[maxn],rr[maxn],n;
    vector<int>lq[maxn],rq[maxn],li[maxn],ri[maxn];
    //分别用来存询问左端点、右端点和ll、rr的位置情况
    //vector可以用链式前向星代替,常数小。不过我懒了。。。
    struct Question{
    int l,r;
    long long ans;
    }q[maxn];
    struct Treap{
    #define ls(x) ls[x]
    #define rs(x) rs[x]
    int dat[maxn],ls[maxn],rs[maxn],ra[maxn],root,cnt;
    void right(int &node){
    int rec=ls(node);
    ls(node)=rs(rec);
    rs(rec)=node;
    node=rec;
    }
    void left(int &node){
    int rec=rs(node);
    rs(node)=ls(rec);
    ls(rec)=node;
    node=rec;
    }
    void insert(int &node,int d){
    if(!node){
    node=++cnt;
    dat[node]=d;
    ra[node]=ran();
    return;
    }
    if(dat[node]<d){
    insert(rs(node),d);
    if(ra[rs(node)]>ra[node])left(node);
    }
    else {
    insert(ls(node),d);
    if(ra[ls(node)]>ra[node])right(node);
    }
    }
    int getpre(int d){
    int ans=0,node=root;
    while(node){
    if(dat[node]<d)ans=max(ans,dat[node]),node=rs(node);
    else node=ls(node);
    }
    return ans;
    }
    int getnex(int d){
    int ans=n+1,node=root;
    while(node){
    if(dat[node]<d)node=rs(node);
    else ans=min(ans,dat[node]),node=ls(node);
    }
    return ans;
    }
    }tr;//平衡树
    #undef ls
    #undef rs
    #define ls(x) (x<<1)
    #define rs(x) (x<<1|1)
    struct Segment_Tree{
    int dat[maxn<<2],tag[maxn<<2];
    inline void update(int node){
    dat[node]=dat[ls(node)]+dat[rs(node)];
    }
    inline void datadown(int l,int r,int node,int d){
    dat[node]+=(r-l+1)
    d;
    tag[node]+=d;
    }
    inline void pushdown(int l,int r,int node){
    int mid=l+r>>1;
    datadown(l,mid,ls(node),tag[node]);
    datadown(mid+1,r,rs(node),tag[node]);
    tag[node]=0;
    }
    void add(int L,int R,int l,int r,int node){
    if(L<=l&&R>=r){
    dat[node]+=r-l+1;
    ++tag[node];
    return;
    }
    if(tag[node])pushdown(l,r,node);
    int mid=l+r>>1;
    if(L<=mid)add(L,R,l,mid,ls(node));
    if(R>mid)add(L,R,mid+1,r,rs(node));
    update(node);
    }
    long long ask(int L,int R,int l,int r,int node){
    if(L<=l&&R>=r)return (long long)dat[node];
    if(tag[node])pushdown(l,r,node);
    int mid=l+r>>1;
    long long ans=0;
    if(L<=mid)ans=ask(L,R,l,mid,ls(node));
    if(R>mid)ans+=ask(L,R,mid+1,r,rs(node));
    return ans;
    }
    }st1,st2,st3;//空间够大开三棵线段树QwQ
    Kafuu Chino(){
    n=read();
    int m=read();
    long long p1=read<long long>(),p2=read<long long>();
    for(register int i=1;i<=n;++i){
    a[i]=read();
    pos[a[i]]=i;
    }
    sort(a+1,a+1+n);
    for(register int i=n;i;--i){
    int L=tr.getpre(pos[a[i]]),R=tr.getnex(pos[a[i]]);
    ll[pos[a[i]]]=L;
    rr[pos[a[i]]]=R;
    if(L!=-inf)li[L].push_back(pos[a[i]]);
    if(R!=inf)ri[R].push_back(pos[a[i]]);
    tr.insert(tr.root,pos[a[i]]);
    }
    for(register int i=1;i<=m;++i)
    q[i].l=read(),q[i].r=read(),lq[q[i].l].push_back(i),rq[q[i].r].push_back(i);
    for(register int i=n;i;--i){
    for(register int j=0;j<li[i].size();++j){
    if(li[i][j]+1<rr[li[i][j]])st1.add(li[i][j]+1,rr[li[i][j]]-1,0,n+1,1);
    st3.add(rr[li[i][j]],n+1,0,n+1,1);
    }
    for(register int j=0;j<lq[i].size();++j)
    q[lq[i][j]].ans+=st1.ask(i,q[lq[i][j]].r,0,n+1,1)p2+st3.ask(q[lq[i][j]].r,q[lq[i][j]].r,0,n+1,1)p1;
    }
    for(register int i=1;i<=n;++i){
    for(register int j=0;j<ri[i].size();++j)
    if(ll[ri[i][j]]+1<ri[i][j])st2.add(ll[ri[i][j]]+1,ri[i][j]-1,0,n+1,1);
    for(register int j=0;j<rq[i].size();++j)
    q[rq[i][j]].ans+=st2.ask(q[rq[i][j]].l,i,0,n+1,1)p2;
    }
    for(register int i=1;i<=m;++i)
    printf("%d ",q[i].ans+(long long)(q[i].r-q[i].l)
    p1);
    }

    (博主)还留个问题:为什么线段树上的操作都必须在0到n+1上进行?希望有人能回答,谢谢。

  • 相关阅读:
    时空权衡之计数排序
    何时发生隐式类型转换
    常量指针与指针常量的区别
    虚函数有关面试题
    C++中数组定义及初始化
    InputStream类的available()方法
    FORK()函数
    面向对象三大基本特性,五大基本原则
    SpringMVC工作原理
    java文件的上传
  • 原文地址:https://www.cnblogs.com/little-aztl/p/11144229.html
Copyright © 2020-2023  润新知