关于此题ABC_392_F - Insert [线段树二分][树状数组倍增]的一些总结

传送门

首先需要用到一个trick:单点正向加入操作变成倒序删除操作。

  • 即我们假定现在有序列\(1,2,...,n\),倒序遍历所给序列,对于当前遍历到的\(i\)\(a[i]\),我们发现,如果在现有序列(即前面说的\(1,2,...,n\))中找到最后一个为a[i]的位置\(j\)\(i\)这个数对应答案中的位置就是\(j\),那么我们只需要记录答案,并且\([j + 1,n]\)位置的现有序列全部减一即可。这个过程可以用线段树二分实现。

  • 当然也有更简便的方法,我们直接令新序列\(1,2,...,n\)位置每个数为1,同样的倒序遍历所给序列变成删除操作,只需要对于每个\(i\)\(a[i]\),找到新序列中第一个前缀和为\(a[i]\)的位置\(j\)\(i\)这个数对应答案中的位置就是\(j\),然后令新序列中\(j\)位置的数为0即可。这可以用树状数组倍增来实现,因为涉及到修改操作,所以不能直接前缀和倍增。代码:

#include<bits/stdc++.h>
    
using namespace std;
    
long long t;
const long long N = 5e5 + 10;
long long n,a[N],tr[N],ans[N];

long long lowbit(long long x) {
    return x & (-x);
}

void add(long long lo,long long v) {
    for(;lo <= n;lo += lowbit(lo))
        tr[lo] += v;
}

long long work(long long k) {
    long long p = 0,s = 0;
    for(long long i = 20;i >= 0;i--) {
        if(p + (1 << i) <= n && s + tr[p + (1 << i)] < k)
            s += tr[p + (1 << i)],p += (1 << i);
    }
    return p + 1;
}
    
void solve() {
    cin >> n;
    for(long long i = 1;i <= n;i++)
        cin >> a[i],add(i,1);
    for(long long i = n;i >= 1;i--) {
        long long pos = work(a[i]);
        ans[pos] = i;
        add(pos,-1);
    }
    for(long long i = 1;i <= n;i++) cout << ans[i] << ' ';
}
    
signed main() {
    ios::sync_with_stdio(0);
    cin.tie(0);cout.tie(0);
    t = 1;
    while(t--) solve();
    
    return 0;
}
posted @ 2025-02-11 13:12  孤枕  阅读(27)  评论(0)    收藏  举报