cf1705 E. Mark and Professor Koro

题意:

给定数组。对每次询问 i x,回答把 ai 改成 x 后数组的价值。这种修改是永久的。

定义数组的价值为:

每次选择数组中两个相同的数 x,把它们都删掉然后把 x+1 加入数组,进行任意次操作后数组中最大的数

1n,q,ai,x2e5

思路:

维护一个位数很多的二进制数,最右边为最高位。加入数 x 就是让这二进制数加上一个第 x 位为 1,其他位为 0 的二进制数。数组的价值就是这二进制数的最高位

ai 改成 x 就是减去 ai 再加上 x

+x 就是找位置 x 右边第一个 0 的位置 y,让第 x~y-1 位为 0、第 y 位为 1

x 就是找位置 x 右边第一个 1 的位置 y,让第 x~y-1 位为 1、第 y 位为 0

用set维护 “1” 区间:

const signed N = 5 + 2e5;
int n, q, a[N];

set<PII> S; //记录所有的'1'段,相邻段不会合并,比如可能同时存在{2,3}和{4,4},但似乎没关系
void add(int x) { //加法
    auto it = S.upper_bound({x,INF}); //x,..,[l,r]
    if(it == S.begin() || prev(it)->second < x) //x还不存在
        S.insert({x,x});
    else {
        auto [l,r] = *prev(it); //[l,x,r]
        S.erase({l,r});
        if(l<=x-1) S.insert({l,x-1});
        add(r+1);
    }
}
void sub(int x) { //减法
    auto it = S.upper_bound({x,INF}); //x,..,[l,r]
    if(it == S.begin() || prev(it)->second < x) {
        auto [l,r] = *it;
        S.erase({l,r});
        if(l+1<=r) S.insert({l+1,r});
        S.insert({x,l-1});
    }
    else {
        auto [l,r] = *prev(it); //[l,x,r]
        S.erase({l,r});
        if(l<=x-1) S.insert({l,x-1});
        if(x+1<=r) S.insert({x+1,r});
    }
}

void sol() {
    cin >> n >> q;
    for(int i = 1; i <= n; i++) cin >> a[i], add(a[i]);

    while(q--) {
        int i, x; cin >> i >> x;
        sub(a[i]), add(x), a[i] = x;
        cout << S.rbegin()->second << endl;
    }
}

除此之外也可以权值线段树上二分(找最大值/右边第一个0或1)

发现线段树比set还慢一些,插入建树只比build建树慢一丁点

const signed N = 20 + 2e5;
int n, q, a[N], cnt[N+5]; //cnt开个2e5+log2(2e5)就行了

#define mid (l+r)/2
#define lson u*2
#define rson u*2+1
#define ls lson,l,mid
#define rs rson,mid+1,r

int sum[N*4], lzy[N*4];
void pushup(int u) {
    sum[u] = sum[lson] + sum[rson];
}
void pushdn(int u, int l, int r) {
    if(lzy[u]) {
        sum[lson] += lzy[u] * (mid-l+1), lzy[lson] += lzy[u];
        sum[rson] += lzy[u] * (r - mid), lzy[rson] += lzy[u];
        lzy[u] = 0;
    }
}
void build(int u, int l, int r) {
    if(l == r) {
        sum[u] = cnt[l];
        return;
    }
    build(ls), build(rs);
    pushup(u);
}
void add(int u, int l, int r, int x, int y, int d) {
    if(x <= l && r <= y) {
        sum[u] += d * (r-l+1), lzy[u] += d;
        return;
    }
    pushdn(u, l, r);
    if(x <= mid) add(ls, x, y, d);
    if(y > mid) add(rs, x, y, d);
    pushup(u);
}
int right_first(int u, int l, int r, int x, bool v) { //x位置右边第一个0/1,可以是x
    if(r < x || sum[u] == (v^1)*(r-l+1)) return 0; //区间里一个0/1都没有
    if(l == r) return l;
    pushdn(u, l, r);
    int left_ans = right_first(ls, x, v);
    if(left_ans) return left_ans;
    else return right_first(rs, x, v);
}
int tot_max(int u, int l, int r) {
    if(l == r) return l;
    pushdn(u, l, r);
    if(sum[rson]) return tot_max(rs);
    else return tot_max(ls);
}

void sol() {
    cin >> n >> q;
    for(int i = 1; i <= n; i++) cin >> a[i], cnt[a[i]]++;

    for(int i = 1; i < N; i++) //模拟加法进位
        cnt[i+1] += cnt[i] / 2, cnt[i] %= 2;
    build(1,1,N); //对cnt建树

    //cnt建完树就没用了
    while(q--) {
        int i, x; cin >> i >> x;
        
        int y = right_first(1,1,N, a[i], 1);
        if(a[i] <= y-1) add(1,1,N, a[i], y-1, 1);
        add(1,1,N, y, y, -1);

        a[i] = x;

        y = right_first(1,1,N, a[i], 0);
        if(a[i] <= y-1) add(1,1,N, a[i], y-1, -1);
        add(1,1,N, y, y, 1);

        cout << tot_max(1,1,N) << endl;
    }
}
posted @   Bellala  阅读(22)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示