洛谷 P5044 - [IOI2018] meetings 会议(笛卡尔树+DP+线段树)
阅读原文时间:2023年07月10日阅读:1

洛谷题面传送门

一道笛卡尔树的 hot tea。

首先我们考虑一个非常 naive 的区间 DP:\(dp_{l,r}\) 表示区间 \([l,r]\) 的答案,那么我们考虑求出 \([l,r]\) 中最大值的位置所在的位置 \(p\),那么如果我们选取的 meeting 的位置 \(\le p\),那么显然 \([p+1,r]\) 部分的贡献都是 \(a_p\),\([l,p]\) 部分的总共先最小是 \(dp_{l,p}\),最优代价为 \(dp_{l,p}+a_p·(r-p)\),否则 \([l,p]\) 部分的贡献都是 \(a_p\),\([p+1,r]\) 部分的总贡献最小是 \(dp_{p+1,p}\),最优代价为 \(dp_{p+1,p}+a_p·(p-l+1)\),二者取 \(\min\) 即可。

直接做最优大概是 \(n^2\) 的,可以获得 19 分的好成绩(雾)。考虑优化,注意到这东西有点像笛卡尔树,因此我们考虑将笛卡尔树建出来。对于此题而言,由于数据范围允许 \(n\log n\) 通过,我们可以使用 \(n\log n\) 的 ST 表建树方法,不必采用那个严格线性的方式。我们考虑对于每组询问 \([L,R]\),找到 \([L,R]\) 区间中最大值所在的位置 \(p\),然后将整个询问拆成两个询问:\([L,p-1]\) 和 \([p+1,R]\)——为什么要这样干呢?原理与 P3246 [HNOI2016]序列 一致,因为这样我们就造出了两个区间,一个满足区间左边那个数大于等于区间中所有数,另一个则满足区间右边那个数大于等于区间中所有数(方便起见,对于后面的部分,我们可以将整个数组翻转一遍然后对翻转后的数组再做一遍这个操作,这样两部分就等价了,注意在后面一半建树部分有些小细节,所有值相同的最大值中,取下标最大的还是最小的要想清楚,不要想当然)这样一来,对于笛卡尔树上每个点 \(x\) 我们如果假设 \(x\) 子树内的点表示的区间左端点为 \(L_x\),右端点为 \(R_x\),那么对于满足“左边的数大于等于区间中所有数的区间 \([L,R]\)”,我们必然能够在笛卡尔树上找到某个点 \(x\),满足 \(L_x=L\),也就是说我们可以将每个询问挂在这个区间中所有点在笛卡尔树的 LCA,也就是 \([L,R]\) 最大值所在的位置表示的点上,然后离线一遍 DFS,DFS 的过程中扫描到点 \(x\) 时,用某种方式维护 \([L_x,L_x],[L_x,L_x+1],\cdots,[L_x,R_x]\) 的 DP 值,这样即可查出答案。

那么怎么维护这样些 DP 值呢?我们开一棵线段树,下标为 \(r\) 的位置实时维护右端点为 \(r\) 的区间的 DP 值。那么假设我们遍历到点 \(x\),\([L_x,R_x]\) 最大值所在的位置为 \(p\),那么我们先遍历 \([L_x,p-1],[p+1,R_x]\),那么对于 \([L_x,p-1]\) 中的位置,其 DP 值肯定不会发生变化,合并的时候就不用管它,对于 \(dp_{L_x,p}\),由于区间 \([L_x,p]\) 最大值就是 \(a_p\),因此我们肯定只能令 meeting 的位置 \(\le p\),此时 \(dp_{L_x,p}=dp_{L_x,p-1}+a_p·(p-L_x+1)\),在线段树上查询 \(p-1\) 位置的值然后做出对应修改即可。

比较棘手的是区间 \([p+1,R_x]\)。对于区间 \([p+1,R_x]\) 中的某个 \(v\),根据上面朴素的 DP 有 \(dp_{L_x,v}=\min(dp_{L_x,p}+(v-p)·a_p,dp_{p+1,v}+(p-L_x+1)·a_p)\),不难发现 \(\min\) 左右两边都是递增的,并且右边的部分增长速度严格慢于左边,因此我们考虑二分找出最右边的位置 \(pos\),满足 \(dp_{L_x,p}+(pos-p)·a_p\le dp_{p+1,pos}+(p-L_x+1)·a_p\),然后令 \([p+1,v]\) 位置中的 DP 值先清零,然后对于所有 \(i\in[p+1,v]\),DP 值假设 \((dp_{L_x,p}-p·a_p)+i·a_p\),\([v+1,p]\) 位置上的值整体加上 \((p-L_x+1)·a_p\),那么怎么找到这个位置 \(pos\) 呢?直接二分是 2log 的,不过注意到如果我们令 \(A=dp_{L_x,p}-p·a_p,B=a_p,C=(p-L_x+1)·a_p)\),那么我们等价于找到最右边的位置满足 \(A+Bi\le C+f(i)\),其中 \(f(i)\) 表示线段树上下标为 \(i\)​ 的位置上的值,这个一脸线段树二分的样子,线段树上二分一下即可做到 1log。

时间复杂度 \((n+q)\log n\)。

const int MAXN=7.5e5;
const int LOG_N=20;
int n,qu,a[MAXN+5];
pii qp[MAXN+5];
struct solver{
    pii st[MAXN+5][LOG_N+2];
    pii query(int l,int r){
        int k=31-__builtin_clz(r-l+1);
        return max(st[l][k],st[r-(1<<k)+1][k]);
    }
    int ncnt=0,rt=0,ch[MAXN+5][2],md[MAXN+5];
    void build_cat(int &k,int l,int r){
        if(l>r) return;if(!k) k=++ncnt;md[k]=abs(query(l,r).se);
//        printf("%d %d %d\n",l,r,md[k]);
        build_cat(ch[k][0],l,md[k]-1);build_cat(ch[k][1],md[k]+1,r);
    }
    void init(){
        for(int i=1;i<=LOG_N;i++) for(int j=1;j+(1<<i)-1<=n;j++)
            st[j][i]=max(st[j][i-1],st[j+(1<<i-1)][i-1]);
        build_cat(rt,1,n);
    }
    vector<pii> qv[MAXN+5];
    void add_query(int l,int r,int id){
        if(l>r) return;int ps=abs(query(l,r).se);
//        printf("+ %d %d %d %d\n",l,r,id,ps);
        qv[ps].pb(mp(r,id));
    }
    struct node{int l,r;ll val,lz0,lz1,t0;} s[MAXN*4+5];
    void pushup(int k){s[k].val=s[k<<1|1].val;}
    void build(int k,int l,int r){
        s[k].l=l;s[k].r=r;if(l==r) return;int mid=l+r>>1;
        build(k<<1,l,mid);build(k<<1|1,mid+1,r);
    }
    void make0(int k){s[k].t0=1;s[k].val=s[k].lz0=s[k].lz1=0;}
    void tag(int k,ll v0,ll v1){
        s[k].val+=v0+1ll*s[k].r*v1;
        s[k].lz0+=v0;s[k].lz1+=v1;
    }
    void pushdown(int k){
        if(s[k].t0){make0(k<<1);make0(k<<1|1);s[k].t0=0;}
        if(s[k].lz0||s[k].lz1){
            tag(k<<1,s[k].lz0,s[k].lz1);
            tag(k<<1|1,s[k].lz0,s[k].lz1);
            s[k].lz0=s[k].lz1=0;
        }
    }
    void addrng(int k,int l,int r,ll v0,ll v1){
        if(l>r) return;
        if(l<=s[k].l&&s[k].r<=r) return tag(k,v0,v1),void();
        pushdown(k);int mid=s[k].l+s[k].r>>1;
        if(r<=mid) addrng(k<<1,l,r,v0,v1);
        else if(l>mid) addrng(k<<1|1,l,r,v0,v1);
        else addrng(k<<1,l,mid,v0,v1),addrng(k<<1|1,mid+1,r,v0,v1);
        pushup(k);
    }
    void modify(int k,int p,ll x){
        if(s[k].l==s[k].r) return s[k].val=x,void();
        pushdown(k);int mid=s[k].l+s[k].r>>1;
        (p<=mid)?modify(k<<1,p,x):modify(k<<1|1,p,x);
        pushup(k);
    }
    int walk(int k,int l,int r,ll lft,int dlt,ll rit){//the rightmost index i satisfying lft+dlt*i<=rit+f(i)
        if(s[k].l==s[k].r) return (lft+1ll*dlt*s[k].l<=rit+s[k].val)?s[k].l:(s[k].l-1);
        pushdown(k);int mid=s[k].l+s[k].r>>1;
        if(r<=mid) return walk(k<<1,l,r,lft,dlt,rit);
        else if(l>mid) return walk(k<<1|1,l,r,lft,dlt,rit);
        else{
            if(lft+1ll*dlt*mid<=rit+s[k<<1].val) return walk(k<<1|1,mid+1,r,lft,dlt,rit);
            else return walk(k<<1,l,mid,lft,dlt,rit);
        }
    }
    void makezero(int k,int l,int r){
        if(l>r) return;
        if(l<=s[k].l&&s[k].r<=r) return make0(k),void();
        pushdown(k);int mid=s[k].l+s[k].r>>1;
        if(r<=mid) makezero(k<<1,l,r);
        else if(l>mid) makezero(k<<1|1,l,r);
        else makezero(k<<1,l,mid),makezero(k<<1|1,mid+1,r);
        pushup(k);
    }
    ll query_pt(int k,int p){
        if(s[k].l==s[k].r) return s[k].val;
        pushdown(k);int mid=s[k].l+s[k].r>>1;
        return (p<=mid)?query_pt(k<<1,p):query_pt(k<<1|1,p);
    }
    ll res[MAXN+5];
    void solve(int k,int l,int r){
//        printf("solving %d %d %d\n",k,l,r);
        if(!k) return;
        if(!ch[k][0]&&!ch[k][1]){
            modify(1,l,st[l][0].fi);
            for(pii p:qv[l]) res[p.se]=st[l][0].fi;
            return;
        } int mid=md[k];
        solve(ch[k][0],l,mid-1);solve(ch[k][1],mid+1,r);
        ll val=(!ch[k][0])?st[mid][0].fi:(query_pt(1,mid-1)+st[mid][0].fi);
        modify(1,mid,val);
//        printf("%d %d %d %lld\n",l,r,mid,val);
        if(mid<r){
            int pos=walk(1,mid+1,r,val-1ll*st[mid][0].fi*mid,st[mid][0].fi,1ll*st[mid][0].fi*(mid-l+1));
            makezero(1,mid+1,pos);addrng(1,mid+1,pos,val-1ll*st[mid][0].fi*mid,st[mid][0].fi);
            addrng(1,pos+1,r,1ll*(mid-l+1)*st[mid][0].fi,0);
        } for(pii p:qv[mid]) res[p.se]=query_pt(1,p.fi);
    }
    void work(){build(1,1,n);solve(rt,1,n);}
} fw,bk;
int main(){
    scanf("%d%d",&n,&qu);
    for(int i=1;i<=n;i++) scanf("%d",&a[i]);
    for(int i=1;i<=n;i++) fw.st[i][0]=mp(a[i],i);
    for(int i=1;i<=n;i++) bk.st[i][0]=mp(a[n-i+1],-i);
    fw.init();bk.init();
    for(int i=1;i<=qu;i++){
        scanf("%d%d",&qp[i].fi,&qp[i].se);++qp[i].fi;++qp[i].se;
        int ps=fw.query(qp[i].fi,qp[i].se).se;
//        printf("ps=%d\n",ps);
        fw.add_query(ps+1,qp[i].se,i);
        bk.add_query(n-ps+2,n-qp[i].fi+1,i);
    } fw.work();bk.work();
//    for(int i=1;i<=qu;i++) printf("%lld %lld\n",fw.res[i],bk.res[i]);
    for(int i=1;i<=qu;i++){
        ll r1=fw.res[i],r2=bk.res[i];
        int ps=fw.query(qp[i].fi,qp[i].se).se;
        printf("%lld\n",min(r1+1ll*a[ps]*(ps-qp[i].fi+1),r2+1ll*a[ps]*(qp[i].se-ps+1)));
    }
    return 0;
}