HNOI2017

单旋

Description

给你一棵单旋splay,让你维护执行某些操作之后节点的深度信息。

单旋splay就是说在祖父-父亲-我,三点一线的时候,仍坚持旋转我两次。

双旋就是先旋父亲一次,再旋我一次。

Solution

题目只要求旋转最小值和最大值到根(其实旋转别的似乎也差不多??)。手模一个三层以上的splay可以发现执行完操作后,相当于把该节点移到根,其儿子过继给父亲,树的其他部分形态都不变。

这说明每次操作深度发生变化的点都很少。比如说旋转最小值\(x\)。那么它深度变成1,其余部分除了它儿子深度不变以外,深度都加+1。把点权离散化,因为保证了权值两两不同,直接用其离散化以后的值作为编号,那么深度+1的点就是\([fa[x],maxn]\)这段区间。区间修改,上线段树直接搞定。

旋转最大值同理。

而删除操作,就是所有节点深度-1,也直接线段树就行。

最后还剩下插入操作。直接模拟显然是不行的。考虑找到\(x\)的前驱和后继(前驱是小于x的中最大的,后继是大于\(x\)的中最小的)。显然只有深度较深的那个没有儿子,把\(x\)接上去就可以来。

怎么找到前驱后继?并行维护一个set就可以啦。

所以每次操作,维护节点的父亲和儿子,维护set,维护线段树。

Code

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef set<int>::iterator sit;
inline int read(){//be careful for long long!
    register int x=0,f=1;register char ch=getchar();
    while(!isdigit(ch)){if(ch=='-')f=0;ch=getchar();}
    while(isdigit(ch)){x=x*10+(ch^'0');ch=getchar();}
    return f?x:-x;
}

const int N=1e5+10;
int n,m,fa[N],ch[N][2],a[N],rt;
struct s_query{int op,x;}q[N];
set<int> s;

namespace Segment_Tree{
#define ls(p) p<<1
#define rs(p) p<<1|1
    int tr[N<<2],tag[N<<2];
    inline void pushdown(int p){
    if(!tag[p])return;
    tag[ls(p)]+=tag[p],tag[rs(p)]+=tag[p];
    tr[ls(p)]+=tag[p],tr[rs(p)]+=tag[p];
    tag[p]=0;
    }
    inline void Set(int p,int l,int r,int pos,int v){
    if(l==r){tr[p]=v;return;}
    pushdown(p);int mid=(l+r)>>1;
    if(pos<=mid)Set(ls(p),l,mid,pos,v);
    else Set(rs(p),mid+1,r,pos,v);
    }
    inline void Modify(int p,int l,int r,int L,int R,int v){
    if(L<=l&&r<=R){tr[p]+=v,tag[p]+=v;return;}
    pushdown(p);int mid=(l+r)>>1;
    if(L<=mid)Modify(ls(p),l,mid,L,R,v);
    if(mid<R)Modify(rs(p),mid+1,r,L,R,v);
    }
    inline int Query(int p,int l,int r,int pos){
    if(l==r)return tr[p];
    pushdown(p);int mid=(l+r)>>1;
    if(pos<=mid)return Query(ls(p),l,mid,pos);
    else return Query(rs(p),mid+1,r,pos);
    }
}using namespace Segment_Tree;

inline sit Pre(sit it){return --it;}
inline void Insert(int x){
    if(!s.size()){s.insert(x);Set(1,1,m,x,1);rt=x;puts("1");return;}
    int pre=(*Pre(s.lower_bound(x))),nxt=(*s.upper_bound(x));
    int d_pre=Query(1,1,m,pre),d_nxt=Query(1,1,m,nxt),d_x;
    if(s.lower_bound(x)!=s.begin()&&!ch[pre][1]){ch[pre][1]=x;fa[x]=pre;d_x=d_pre+1;}
    if(!fa[x]){ch[nxt][0]=x;fa[x]=nxt;d_x=d_nxt+1;}
    Set(1,1,m,x,d_x);s.insert(x);
    printf("%d\n",d_x);
}
inline void Splay(int type){
    if(!type){
    int p=(*s.begin());printf("%d\n",Query(1,1,m,p));
    if(p==rt)return;
    Set(1,1,m,p,1);Modify(1,1,m,fa[p],m,1);
    ch[fa[p]][0]=ch[p][1];fa[ch[p][1]]=fa[p];
    fa[rt]=p;fa[p]=0;ch[p][1]=rt;rt=p;
    }
    else{
    int p=(*Pre(s.end()));printf("%d\n",Query(1,1,m,p));
    if(p==rt)return;
    Set(1,1,m,p,1);Modify(1,1,m,1,fa[p],1);
    ch[fa[p]][1]=ch[p][0];fa[ch[p][0]]=fa[p];
    fa[rt]=p;fa[p]=0;ch[p][0]=rt;rt=p;
    }
}
inline void Pop(int type){
    Splay(type);s.erase(rt);
    Modify(1,1,m,1,m,-1);
    rt=ch[rt][type^1];fa[rt]=0;
}

int main(){
//    freopen("splay5.in","r",stdin);freopen("out.out","w",stdout);
    n=read();
    for(int i=1;i<=n;++i){q[i].op=read();if(q[i].op==1)q[i].x=a[++m]=read();}
    sort(a+1,a+m+1);
    for(int t=1;t<=n;++t){
    switch(q[t].op){
    case 1:{
        int x=lower_bound(a+1,a+m+1,q[t].x)-a;
        Insert(x);break;
    }
    case 2:Splay(0);break;
    case 3:Splay(1);break;
    case 4:Pop(0);break;
    case 5:Pop(1);break;
    }
    }
    return 0;
}

影魔

Description

暴力是st表预处理出区间最大值,然后\(\text O(n^2)\)枚举\(i,j\)计算贡献,没有什么优化空间,考虑换一种枚举方式。

每一对\((i,j)\)做贡献,其实也可以看作中间的最大值\(k_i\)的贡献。

所以我们尝试从枚举\(k_i\)的角度入手。先求出从\(k_i\)向左走,第一个比它大的位置,记为\(L_i\);还有向右走第一个比它大的位置,记为\(R_i\)。如果区间再往两边延伸,\(k_i\)就不是区间的最大值了,所以\([L_i,R_i]\)就是它的“影响范围”。所有的\(L,R\)可以用一个单调栈从左往右扫一遍求出。

对于每一个\(k_i\),我们考虑一下它的贡献:

  • \(L_i,R_i\)作为左右端点。有\(p_1\)的贡献。
  • \(L_i\)作为左端点,\([i+1,R_i-1]\)作为右端点。每一对有\(p_2\)的贡献。
  • \([L_i+1,i-1]\)作为左端点,\(R_i\)作为右端点。每一对有\(p_2\)的贡献。

发现第二种和第三种点对的贡献,有某一个端点是一段区间。可以把贡献挂链,挂在另一个孤立的端点处。这样每次计算贡献,扫到那个孤立端点时,区间增加贡献即可。

例如第二种,可以把\([i+1,R_i-1]\)的区间放到\(L_i\),当扫到\(L_i\)时,把贡献加上。第三种同理。

而对于第一种,就是单点修改,我们看作把\([R_i,R_i]\)挂在\(L_i\)处,或者\([L_i,L_i]\)挂在\(R_i\)处都行。

现在做法显然了。

用一棵线段树维护贡献。从左往右枚举端点,每扫到一个点\(i\),把挂在其下的贡献加到线段树上。

询问考虑差分解决。对于一个询问\([l,r]\),拆成两个询问\(l-1\)\(r\)

扫到\(l-1\)时,记\([l,r]\)的贡献和为\(sum1\);扫到\(r\)时,记\([l,r]\)贡献和为\(sum2\)。那么答案就是\(sum2-sum1\)

Code

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
inline int read(){//be careful for long long!
    register int x=0,f=1;register char ch=getchar();
    while(!isdigit(ch)){if(ch=='-')f=0;ch=getchar();}
    while(isdigit(ch)){x=x*10+(ch^'0');ch=getchar();}
    return f?x:-x;
}

const int N=2e5+10;
int n,m,p1,p2,a[N],L[N],R[N],stk[N],tp,tot;
ll ans[N];
struct s_node{int l,r,p,v,id;inline bool operator <(const s_node &a)const{return p<a.p;}}op[N*3],q[N<<1];

namespace Segment_Tree{
    ll tr[N<<2],tag[N<<2];
#define ls(p) p<<1
#define rs(p) p<<1|1
    inline void pushdown(int p,int l,int r){
    if(!tag[p])return;
    tag[ls(p)]+=tag[p],tag[rs(p)]+=tag[p];
    int mid=(l+r)>>1;
    tr[ls(p)]+=tag[p]*(mid-l+1),tr[rs(p)]+=tag[p]*(r-mid);
    tag[p]=0;
    }
    inline void update(int p){tr[p]=tr[ls(p)]+tr[rs(p)];}
    inline void Modify(int p,int l,int r,int L,int R,int v){
    if(L<=l&&r<=R){tr[p]+=1ll*(r-l+1)*v;tag[p]+=v;return;}
    pushdown(p,l,r);int mid=(l+r)>>1;
    if(L<=mid)Modify(ls(p),l,mid,L,R,v);
    if(R>mid)Modify(rs(p),mid+1,r,L,R,v);
    update(p);
    }
    inline ll Query(int p,int l,int r,int L,int R){
    if(L<=l&&r<=R)return tr[p];
    pushdown(p,l,r);int mid=(l+r)>>1;ll ans=0;
    if(L<=mid)ans+=Query(ls(p),l,mid,L,R);
    if(R>mid)ans+=Query(rs(p),mid+1,r,L,R);
    return ans;
    }
}using namespace Segment_Tree;

int main(){
    n=read(),m=read();p1=read(),p2=read();
    for(int i=1;i<=n;++i)a[i]=read();
    a[n+1]=n+1;
    for(int i=1;i<=n+1;++i){
    while(tp&&a[stk[tp]]<=a[i])R[stk[tp]]=i,--tp;
    L[i]=stk[tp];stk[++tp]=i;
    }
    for(int i=1;i<=n;++i){
    if(1<=L[i]&&R[i]<=n)op[++tot]=(s_node){L[i],L[i],R[i],p1,0};
    if(L[i]<i-1&&R[i]<=n)op[++tot]=(s_node){L[i]+1,i-1,R[i],p2,0};
    if(R[i]>i+1&&L[i]>=1)op[++tot]=(s_node){i+1,R[i]-1,L[i],p2,0};
    }
    sort(op+1,op+tot+1);
    for(int i=1;i<=m;++i){
    int l=read(),r=read();
    ans[i]+=1ll*(r-l)*p1;//(l,r)==empty;
    q[i]=(s_node){l,r,l-1,-1,i};
    q[m+i]=(s_node){l,r,r,1,i};
    }
    sort(q+1,q+m+m+1);
    int o_t=1,q_t=1;while(!q[q_t].p&&q_t<=m+m)++q_t;
    for(int i=1;i<=n;++i){
    while(op[o_t].p<=i&&o_t<=tot)Modify(1,1,n,op[o_t].l,op[o_t].r,op[o_t].v),++o_t;
    while(q[q_t].p<=i&&q_t<=m+m)ans[q[q_t].id]+=Query(1,1,n,q[q_t].l,q[q_t].r)*q[q_t].v,++q_t;
    }
    for(int i=1;i<=m;++i)printf("%lld\n",ans[i]);
    return 0;
}

猜你喜欢

转载自www.cnblogs.com/fruitea/p/12093164.html
今日推荐