CF 1405E Fixed Point Removal【线段树上二分】

CF 1405E Fixed Point Removal【线段树上二分】 

题意:

给定长度为\(n\)的序列\(A\),每次操作可以把\(A_i = i\)(即值等于其下标)的数删掉,然后剩下的数组拼接起来,问最多能删多少个数

\(q\)次独立询问,每次把前\(x\)个数和\(后\)\(y\)个数置为\(n+1\)之后解决上述问题

题解:

先不考虑把前\(x\)个数和后\(y\)个数置成\(n+1\)的情况

首先我们可以想到的是把所有数的值减去其下标,定义\(B_i = A_i - i\),那么对于\(B_i=0\)的位置,一开始就是可以删除的,当删掉了第\(i\)个数之后,之前的数的下标没有变化,之后的数下标都减了\(1\),即对于所有\(j>i\)\(B_j\)变成了\(B_j+1\),也就可能存在新的\(B_j=0\)的情况

根据上面的事实,那么一开始\(B_i>0\)的这些值永远也删不掉,且为了删除最多的数,每次肯定选择最右边的\(B_i=0\)的值进行删除(如果存在下标\(i,j\)\(i<j,B_i=B_j=0\),如果先删除\(i\),那么\(j\)就删不掉了)

现在有一个推论:对于某个位置\(i\),如果\(B_i<=0\),那么在它之前至少存在\(-B_i\)个数被删掉,这个数就也可以被删除

那么对于每个位置\(i\),满足\(B_i\le0\),存在一个左边界\(l\),只要第\(l\)个元素能够删除,那么第\(i\)个元素也能被删除

左边界\(l\)需要满足只考虑\([l,i)\)区间的元素的情况下,可删除元素大于等于\(-B_i\)个,如果\(B_i=0\)显然\(l=i\)

那么对于每个存在左边界的\(i\)需要做的就是在\(l\)的位置加上\(1\),每个位置的值就表示以这个点为左边界的情况下能删除多少个数,找\(l\)的方法可以二分之后算区间和或者直接在线段树上二分,前者复杂度\(O(\log^2n)\)后者复杂度\(O(\log n)\)

考虑先把询问按右边界从小到大排序(即\(y\)从大到小排序),遍历每个询问,更新到\(i=n-y\)的位置,然后计算\([x+1,n-y]\)的区间和就好了

排序是因为要防止当前不合法的点对左边界产生贡献

view code
#pragma GCC optimize("O3")
#pragma GCC optimize("Ofast,no-stack-protector")
#include<bits/stdc++.h>
using namespace std;
#define INF 0x3f3f3f3f
#define endl "\n"
#define LL long long int
#define vi vector<int>
#define vl vector<LL>
#define all(V) V.begin(),V.end()
#define sci(x) scanf("%d",&x)
#define scl(x) scanf("%lld",&x)
#define scs(s) scanf("%s",s)
#define pii pair<int,int>
#define pll pair<LL,LL>
#ifndef ONLINE_JUDGE
#define cout cerr
#endif
#define cmax(a,b) ((a) = (a) > (b) ? (a) : (b))
#define cmin(a,b) ((a) = (a) < (b) ? (a) : (b))
#define debug(x)  cerr << #x << " = " << x << endl
function<void(void)> ____ = [](){ios_base::sync_with_stdio(false); cin.tie(0); cout.tie(0);};
template <typename T> vector<T>& operator << (vector<T> &__container, T x){ __container.push_back(x); return __container; }
template <typename T> ostream& operator << (ostream &out, vector<T> &__container){ for(T _ : __container) out << _ << ' '; return out; }
const int MAXN = 3e5+7;
int n, q, A[MAXN];
pair<pii,int> Q[MAXN];
struct SegmentTree{
    int sum[MAXN<<2], l[MAXN<<2], r[MAXN<<2];
    #define ls(rt) rt << 1
    #define rs(rt) rt << 1 | 1
    void build(int L, int R, int rt = 1){
        l[rt] = L; r[rt] = R;
        sum[rt] = 0;
        if(l[rt] + 1 == r[rt]) return;
        int mid = (L + R) >> 1;
        build(L,mid,ls(rt)); build(mid,R,rs(rt));
    }
    void modify(int pos, int x, int rt = 1){
        sum[rt] += x;
        if(l[rt] + 1 == r[rt]) return;
        int mid = (l[rt] + r[rt]) >> 1;
        if(pos<mid) modify(pos,x,ls(rt));
        else modify(pos,x,rs(rt));
    }
    int qsum(int L, int R, int rt = 1){
        if(L>=r[rt] or l[rt]>=R) return 0;
        if(L<=l[rt] and r[rt]<=R) return sum[rt];
        return qsum(L,R,ls(rt)) + qsum(L,R,rs(rt));
    }
    int qpos(int x, int rt = 1){
        if(l[rt] + 1 == r[rt]) return l[rt];
        if(sum[rs(rt)]>=x) return qpos(x,rs(rt));
        else return qpos(x-sum[rs(rt)],ls(rt));
    }
}ST;
int ret[MAXN];
void solve(){
    sci(n); sci(q);
    for(int i = 1; i <= n; i++) sci(A[i]), A[i] -= i;
    for(int i = 1; i <= q; i++){
        sci(Q[i].first.first); sci(Q[i].first.second);
        Q[i].second = i;
    }
    sort(Q+1,Q+1+q,[&](pair<pii,int> &a, pair<pii,int> &b){ return a.first.second > b.first.second; });
    int cur = 1, tot = 0;
    ST.build(0,n+1);
    for(int i = 1; i <= q; i++){
        while(cur<=n-Q[i].first.second){
            if(A[cur]==0) ST.modify(cur,1), tot++;
            else if(A[cur]<0 and -A[cur]<=tot) ST.modify(ST.qpos(-A[cur]),1), tot++;
            cur++;
        }
        ret[Q[i].second] = ST.qsum(Q[i].first.first+1,cur);
    }
    for(int i = 1; i <= q; i++) cout << ret[i] << endl;
}
int main(){
    #ifndef ONLINE_JUDGE
    freopen("Local.in","r",stdin);
    freopen("ans.out","w",stdout);
    #endif
    solve();
    return 0;
}
posted @ 2020-09-07 13:32  _kiko  阅读(313)  评论(0编辑  收藏  举报