P10430 [JOIST 2024] 鱼 3 / Fish 3 题解(做法二)

前面写过一个另外一个方法的,我自己想的。那个方法拉完了,实现非常复杂。

老题解

P10430 [JOIST 2024] 鱼 3 / Fish 3

注意,下面把问题转化为智商为\(c_i\),让智商降低为\(0\)。也就是说,所有操作的加减反转

我们容易发现,对于一个询问。等价于我们要用最少的操作A的次数使得区间单调不降。

我们先考虑暴力做法。

我们先确定区间的右端点。

记当前枚举的区间为 \([l,r]\)

然后我们从右往做枚举。对于\(l \leq i,i+1 \leq r\)\(c_i>c_i+1\)。 则意味着\(c_i\)要减小,直到不大于\(c_{i+1}\)

然后我们很容易发现一个性质,对于任意一个连续的区间\([l',r']\) 诺这个区间内相连两个数的差均小于\(d\)\(c_{r'}\) 减少\(kd\),整个\([l',rl]\)区间内所有的数都至少得跟着减少相同的值。

这使得我们不用一个个枚举。一次可以之间枚举一个区间。

我们将一个相连两个数的差均小于\(d\)的区间称做绑定区间。

对于两个相邻的绑定区间 \(A,B\) \(B\)\(A\)左侧显然,有时候\(B\)减少\(A\)也得跟着减少。

我们发现,这个现象出现在\(A,B\) 两个区间之间的差小于\(d\)的时候。在小于\(d\)\(A\)\(B\)可以构成一个新的绑定区间。

那么我们在处理的时候,可以尝试合并相连的绑定区间。这样以后的复杂度会更优。

显然,合并区间的总复杂度为\(O(n)\)

然后我们如何实现区间整体减法,然后判断两个区间相连处的差呢?很显然,线段树解决。

但是每次询问都要经理这个过程还是太慢了。

我们考虑离线处理。

我们从对于每个区间内的枚举,变成整体\([1,n]\)的枚举

然后我们每次枚举完\(i\),就计算那些右端点为\(i\)的区间的答案。

我们先考虑什么情况下不合法。我们维护的是最少的操作\(A\)的次数,只有在某个元素经不起这么多次操作\(A\)的时候,这个区间无法完成任务。

具体的,我们每次区间减法的时候维护一下\(c\)的具体值,然后看询问区间内有没有具体值为负数的。如果有,这个区间就无法完任务。

诺可以完成任务,我们如何计算进行了多少次\(A\)操作呢?显然,我们前面每次\(A\)操作的时候进行了区间线段树维护。那区间进行\(A\)操作的次数也就顺便维护了。

当然,你不肯一个个减去\(d\),你肯定得计算方法一次减去多个\(d\)的,这个非常简单。

复杂度多少?

\(n\)次合并\(n\)次枚举\(q\)个询问

\(O((n+q)log_2n)\)

#include <bits/stdc++.h>

#define ls(x) ((x)<<1)
#define rs(x) ((x)<<1|1)

using namespace std;

typedef long long ll;

const ll inf=1e18;
const ll maxn=3e5+5;

inline ll read() {
    long long x = 0, f = 1;
    char ch = getchar();
    while (ch < '0' || ch > '9') {
        if (ch == '-') f = -1;
        ch = getchar();
    }
    while (ch >= '0' && ch <= '9') {
        x = x * 10 + (ch - '0');
        ch = getchar();
    }
    return x * f;
}

struct Task{
    ll l,r,id;
};

struct node{
    ll sum,tag,minn; //cnt代表-d了多少次。tag代表懒标记。min代表在arr序列上修改后的值。
};

struct range{
    ll l,r;
};

node seg[maxn<<2];// 线段树维护操作数
stack<range> sta;
ll arr[maxn],ans[maxn],up[maxn];
Task task[maxn];
ll n,d,q;

void push_down(ll p,ll l,ll r) {
    if(!seg[p].tag) {
        return;
    }
    ll mid=(l+r)>>1;
    seg[ls(p)].tag+=seg[p].tag;
    seg[ls(p)].sum+=seg[p].tag*(mid-l+1);
    seg[ls(p)].minn-=seg[p].tag*d;
    seg[rs(p)].tag+=seg[p].tag;
    seg[rs(p)].sum+=seg[p].tag*(r-mid);
    seg[rs(p)].minn-=seg[p].tag*d;
    seg[p].tag=0;
}

void push_up(ll p) {
    seg[p].sum=seg[ls(p)].sum+seg[rs(p)].sum;
    seg[p].minn=min(seg[ls(p)].minn,seg[rs(p)].minn);
    return;
}

void upd(ll p,ll l,ll r,ll x,ll y,ll z) {
    if(x<=l&&r<=y) {
        seg[p].tag+=z;
        seg[p].sum+=(r-l+1)*z;
        seg[p].minn-=z*d;
        return;
    }
    push_down(p,l,r);
    ll mid=(l+r)>>1;
    if(x<=mid) {
        upd(ls(p),l,mid,x,y,z);
    }
    if(y>mid) {
        upd(rs(p),mid+1,r,x,y,z);
    }
    push_up(p);
    return;
}

ll ask(ll p,ll l,ll r,ll x,ll y) {
    if(x<=l&&r<=y) {
        return seg[p].sum;
    }
    push_down(p,l,r);
    ll mid=(l+r)>>1,ret=0;
    if(x<=mid) {
        ret+=ask(ls(p),l,mid,x,y);
    }
    if(y>mid) {
        ret+=ask(rs(p),mid+1,r,x,y);
    }
    return ret;
}

ll query(ll p,ll l,ll r,ll x,ll y) {
    if(x<=l&&r<=y) {
        return seg[p].minn;
    }
    push_down(p,l,r);
    ll mid=(l+r)>>1,ret=inf;
    if(x<=mid) {
        ret=query(ls(p),l,mid,x,y);
    }
    if(y>mid) {
        ret=min(query(rs(p),mid+1,r,x,y),ret);
    }
    return ret;
}

ll get(ll x) {
    return query(1,1,n,x,x);
}

void build(ll p,ll l,ll r) {
    if(l==r) {
        seg[p].minn=arr[l];
        return;
    }
    //push_down(p,l,r);
    ll mid=(l+r)>>1;
    build(ls(p),l,mid);
    build(rs(p),mid+1,r);
    push_up(p);
    return;
}



void merge() {
    if(sta.size()<2) {
        return;
    }
    range tmp;
    tmp.r=sta.top().r;
    sta.pop();
    tmp.l=sta.top().l;
    sta.pop();
    sta.push(tmp);
    return;
}

void show() {
    for(ll i=1;i<=n;++i) {
        printf("%lld ",get(i));
    }
    puts("");
}

int main() {
   // freopen("input.txt","r",stdin);

    n=read();
    d=read();

    for(ll i=1;i<=n;++i) {
        arr[i]=read();
    }

    q=read();
    for(ll i=1;i<=q;++i) {
        cin>>task[i].l>>task[i].r;
        task[i].id=i;
    }


    sort(task+1,task+1+q,[](const Task&a,const Task&b){
        return a.r<b.r;
    });

    build(1,1,n);
    sta.push({1,1});
    ll tot=1;
    while(task[tot].r==1) {
        ans[task[tot].id]=0;
        ++tot;
    }
    for(ll i=2;i<=n;++i) {
        //show();
        ll pre=get(i-1);
        ll now=get(i);
        if(now>pre) {
            //构成新的连续段或者加入旧的连续段
            if(now-pre>=d) {
                //构成新的连续段‘
                sta.push({i,i});
            }else{
                //插入旧的连续段
                sta.push({i,i});
                merge();
            }
        }else{
            //我们尝试-d.
            ll div=(get(i-1)-get(i))/d;
            if((get(i-1)-get(i))%d) {
                ++div;
            }
            while(div) {
                //分类,是否要合并s
                if(sta.top().l==1) {
                    //肯定不需要合并了 没法合并
                    upd(1,1,n,1,sta.top().r,div);
                    div=0;
                    continue;
                }
                //检查是否需要合并
                ll tmp=(get(sta.top().l)-get(sta.top().l-1))/d;
                if(div>=tmp) {
                    //需要合并
                    upd(1,1,n,sta.top().l,sta.top().r,tmp);
                    merge();
                    div-=tmp;

                }else{
                    //不需要合并
                    upd(1,1,n,sta.top().l,sta.top().r,div);
                    div=0;
                }
            }
            sta.push({i,i});
            merge();
        }
        //show();
        //算答案
        while(tot<=q && task[tot].r==i) {
            Task now=task[tot];
            ++tot;
            if(query(1,1,n,now.l,now.r)<0) {
                ans[now.id]=-1;
                continue;
            }
            ans[now.id]=ask(1,1,n,now.l,now.r);
        }
    }

    for(ll i=1;i<=q;++i) {
        printf("%lld\n",ans[i]);
    }




    return 0;
}
posted @ 2026-08-09 17:39  txp2025  阅读(10)  评论(0)    收藏  举报