CF 1208D Restore Permutation 线段树

博客内容讲述了如何解决一个计算机科学中的排列恢复问题。通过分析每个数字的贡献,确定了1的位置,并提出利用线段树的数据结构,通过维护区间最小值和单点更新、区间更新来动态找到并归位每个数字的位置,从而逐步重建原始排列。
摘要由CSDN通过智能技术生成

现在藏起来一个1到n(2e5)的排列, 对于每个位置i, 给出a[1]到a[i-1]中小于a[i]的数的和s[i], 让我们求这个排列a.
首先我们找找1的位置.
因为没有数比1小, 所以不论1在哪里, 它的s肯定是0;
而如果s中有多个0, 1一定在最后一个0—因为如果1在其他0的位置, 最后一个0没法解释—不论最后一个数是什么, 它肯定比1大, 前面的1一定会对这个数产生贡献, 这样我们就证明了1肯定在最后一个0的位置.
自然我们想到把1到n每个数按照这样的方式一个个归位.

一开始我的思路是这样的:
1的位置已经确定, 2的位置要么在1前面, 要么在1后面. 如果在1前, 那么它一定是1前最后一个0; 如果在1后, 它一定是1后面最后一个1. 然后一个个确定.
但是这样是不行的. 且不说程序难以实现, 单是判断在前在后就无从下手.

换一个思路(zl666):
每当将一个数归位, 我们就将它产生的贡献去除, 这样我们每次只须去数组中找最后一个0就好.
具体地, 找到1的位置pos, s数组从pos开始后面全减1; 最后一个0就是2的位置pos2, s从pos2后面全减2; 最后一个0就是3的位置pos3, s从pos3后面全减3······
我们可能要问, 如果2在1前, 那么1的的位置会不会再被用到? 所以我们要在确定1的位置后把1的位置剔除考虑范围, 具体操作其实可以把s[1]赋为INF, 保证它在所有可能的-2/-3/-4/···中不会减到0就好了.
那么这个题就转化为求s数组最后一个0的位置就是i的位置, 然后把它改成INF, 它后面所有的数-i, 再找最后一个0的位置···

这个过程可以用线段树解决:
维护区间最小值, 如果右区间最小值是0, 那么往右走; 否则往左走. 这样就能log(n)找到最后一个0的位置.
改成INF就是单点更新, pos+1到n减i就是区间更新. lazy维护减了多少.

代码:


ll n, a[M];
struct node {
    ll lz, mx;
} tree[M * 4];

void build(int pos, int l, int r) {
    if (l == r) {
        tree[pos].mx = tree[pos].lz = read();
        return;
    }
    int mid = (l + r) / 2;
    build(pos * 2, l, mid);
    build(pos * 2 + 1, mid + 1, r);
    tree[pos].mx = sml(tree[pos * 2].mx, tree[pos * 2 + 1].mx);
}

void pushDown(int pos) {
    tree[pos * 2].lz += tree[pos].lz;
    tree[pos * 2 + 1].lz += tree[pos].lz;
    tree[pos * 2].mx += tree[pos].lz;
    tree[pos * 2 + 1].mx += tree[pos].lz;
    tree[pos].lz = 0;
}

void pushUp(int pos) {
    tree[pos].mx = sml(tree[pos * 2].mx, tree[pos * 2 + 1].mx);
}

int query(int pos, int l, int r) {
    if (l == r)return l;
    pushDown(pos);
    int mid = (l + r) / 2;
    if (tree[pos * 2 + 1].mx == 0)query(pos * 2 + 1, mid + 1, r);
    else query(pos * 2, l, mid);
}

void update1(int pos, int l, int r, int p) {
    if (l == r && l == p) {
        tree[pos].mx = INF;
        return;
    }
    pushDown(pos);
    int mid = (l + r) / 2;
    if (p <= mid)update1(pos * 2, l, mid, p);
    else update1(pos * 2 + 1, mid + 1, r, p);
    pushUp(pos);
}

void update2(int pos, int l, int r, int ul, int ur, int v) {
    if (ul <= l && ur >= r) {
        tree[pos].mx -= v;
        tree[pos].lz -= v;
        return;
    }
    pushDown(pos);
    int mid = (l + r) / 2;
    if (ul <= mid)update2(pos * 2, l, mid, ul, ur, v);
    if (ur > mid)update2(pos * 2 + 1, mid + 1, r, ul, ur, v);
    pushUp(pos);
}


void init() {
    n = read();
    build(1, 1, n);
    for (int i = 1; i <= n; ++i) {
        int pos = query(1, 1, n);
        a[pos] = i;
        update1(1, 1, n, pos);
        update2(1, 1, n, pos, n, i);
    }
    for (int i = 1; i <= n; ++i)write(a[i]), space;
    enter;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值