P2617 Dynamic Rankings题解

P2617
单点修改,区间查询第 \(k\) 小。

树套树

区间第 \(k\) 小的一种解法是用主席树,利用两个子树做差实现。
但加上单点修改后,直接做每次修改是 \(O(n\log n)\) 的,复杂度太大。

考虑平衡询问和修改的复杂度。
我们在主席树外套一棵树状数组
询问时把两个子树作差变成 \(\log n\) 个子树作差。
修改时只需要修改 \(\log n\) 个子树即可。

时间复杂度:\(O(n \log^2 n)\)
空间复杂度:\(O(n \log n)\)

代码
#include<bits/stdc++.h>
using namespace std;
#define endl '\n'
#define FL(a,b,c) for(int a=(b),a##end=(c);a<=a##end;++a)
#define FR(a,b,c) for(int a=(b),a##end=(c);a>=a##end;--a)
#define lowbit(x) ((x)&-(x))
#define eb emplace_back
#define sz(x) (int)((x).size())
#define int long long
#define vt vector
#define fr first
#define se second
#define mmt(x, y) memset(x, y, sizeof x)
#define PII pair<int, int>
#define max(a, b)({auto f7r=(a);auto j3h=(b);f7r<j3h?j3h:f7r;})
#define cmax(a, b)({auto j3h=(b);(j3h>a)&&(a=j3h);})
#define min(a, b)({auto f7r=(a);auto j3h=(b);f7r>j3h?j3h:f7r;})
#define cmin(a, b)({auto j3h=(b);(j3h<a)&&(a=j3h);})
constexpr int N = 1e5 + 10;
int n, m, D[N << 1], a[N], tot, len, rt[N];
struct A{int l, r, k;}Q[N];
struct B{int ls, rs, s;}sg[N * 400];
void modify(int&u, int l, int r, int pos, int v){
    if(!u)u = ++tot;
    if(l == r)return void(sg[u].s += v);
    int mid = l + r >> 1;
    if(pos <= mid)modify(sg[u].ls, l, mid, pos, v);
    else modify(sg[u].rs, mid + 1, r, pos, v);
    sg[u].s = sg[sg[u].ls].s + sg[sg[u].rs].s;
}
void modify(int pos, int v){
    int k = lower_bound(D + 1, D + 1 + len, a[pos]) - D, i = pos;
    while(i <= n)modify(rt[i], 1, len, k, v), i += lowbit(i);
}
int tmp[2][30], cnt[2];
int query(int l, int r, int k){
    if(l == r)return l;
    int mid = l + r >> 1, s = 0;
    FL(i, 1, cnt[1])s += sg[sg[tmp[1][i]].ls].s;
    FL(i, 1, cnt[0])s -= sg[sg[tmp[0][i]].ls].s;
    FL(i, 1, cnt[1])tmp[1][i] = (k > s ? sg[tmp[1][i]].rs : sg[tmp[1][i]].ls);
    FL(i, 1, cnt[0])tmp[0][i] = (k > s ? sg[tmp[0][i]].rs : sg[tmp[0][i]].ls);
    return (k <= s) ? query(l, mid, k) : query(mid + 1, r, k - s);
}
int ask(int l, int r, int k){
    mmt(tmp, 0), cnt[0] = cnt[1] = 0;
    while(r)tmp[1][++cnt[1]] = rt[r], r -= lowbit(r);
    while(l)tmp[0][++cnt[0]] = rt[l], l -= lowbit(l);
    return query(1, len, k);
}
int32_t main(){
    cin.tie(0)->sync_with_stdio(0);
    cin >> n >> m;
    FL(i, 1, n)cin >> a[i], D[++len] = a[i];
    FL(i, 1, m){
        char op;
        cin >> op;
        if(op == 'Q')cin >> Q[i].l >> Q[i].r >> Q[i].k;
        else Q[i].l = 0, cin >> Q[i].r >> Q[i].k, D[++len] = Q[i].k;
    }
    sort(D + 1, D + 1 + len), len = unique(D + 1, D + 1 + len) - D - 1;
    FL(i, 1, n)modify(i, 1);
    FL(i, 1, m){
        if(Q[i].l)cout << D[ask(Q[i].l - 1, Q[i].r, Q[i].k)] << endl;
        else modify(Q[i].r, -1), a[Q[i].r] = Q[i].k, modify(Q[i].r, 1);
    }
    return 0;
}

分块

先对序列分块,再对值域分块。
我们记两个数组:

  1. \(s1_{i,j}\) 为前 \(i\) 块中值域在第 \(j\) 个的数的数量。
  2. \(s2_{i,j}\) 为前 \(i\) 块中 \(j\) 出现的数量。

预处理复杂度 \(O(n\sqrt n)\)

每次查询先找到答案所在的块,再找到答案即可。
修改只是单点,所以直接暴力。

这种做法可以推广到区间修改,例题

时间复杂度:\(O(n\sqrt n)\)
空间复杂度:\(O(n\sqrt n)\)

代码
#include<bits/stdc++.h>
using namespace std;
#define endl '\n'
#define FL(a,b,c) for(int a=(b),a##end=(c);a<=a##end;++a)
#define FR(a,b,c) for(int a=(b),a##end=(c);a>=a##end;--a)
#define lowbit(x) ((x)&-(x))
#define eb emplace_back
#define sz(x) (int)((x).size())
#define int long long
#define vt vector
#define fr first
#define se second
#define mmt(x, y) memset(x, y, sizeof x)
#define PII pair<int, int>
#define max(a, b)({auto f7r=(a);auto j3h=(b);f7r<j3h?j3h:f7r;})
#define cmax(a, b)({auto j3h=(b);(j3h>a)&&(a=j3h);})
#define min(a, b)({auto f7r=(a);auto j3h=(b);f7r>j3h?j3h:f7r;})
#define cmin(a, b)({auto j3h=(b);(j3h<a)&&(a=j3h);})
constexpr int N = 1e5 + 10, M = 400;
int n, m, D[N * 2], a[N], len, L[M], R[M], b1, b2;
int s1[M][M], s2[M][N * 2], id1[N], id2[N * 2], t1[M], t2[N * 2];
struct A{int l, r, k;}Q[N];
int query(int l, int r, int k){
    if(id1[l] == id1[r]){
        FL(i, l, r)t2[i] = a[i];
        nth_element(t2 + l, t2 + l + k - 1, t2 + r + 1);
        int ans = t2[l + k - 1];
        FL(i, l, r)t2[i] = 0;
        return ans;
    }
    int s;
    FL(i, l, R[id1[l]])t1[id2[a[i]]]++, t2[a[i]]++;
    FR(i, r, L[id1[r]])t1[id2[a[i]]]++, t2[a[i]]++;
    FL(i, 1, id2[len]){
        if(k > (s = t1[i] + s1[id1[r] - 1][i] - s1[id1[l]][i]))k -= s;
        else{
            FL(j, (i - 1) * b2 + 1, i * b2){
                if(k > (s = t2[j] + s2[id1[r] - 1][j] - s2[id1[l]][j]))k -= s;
                else{
                    FL(i, l, R[id1[l]])t1[id2[a[i]]]--, t2[a[i]]--;
                    FR(i, r, L[id1[r]])t1[id2[a[i]]]--, t2[a[i]]--;
                    return j;
                }
            }
        }
    }
    return 0;
}
void modify(int pos, int k){
    FL(i, id1[pos], id1[n])
        s1[i][id2[a[pos]]]--, s2[i][a[pos]]--,
        s1[i][id2[k]]++, s2[i][k]++;
    a[pos] = k;
}
int32_t main(){
    cin.tie(0)->sync_with_stdio(0);
    cin >> n >> m;
    FL(i, 1, n)cin >> a[i], D[++len] = a[i];
    FL(i, 1, m){
        char op;cin >> op;
        if(op == 'Q')cin >> Q[i].l >> Q[i].r >> Q[i].k;
        else Q[i].l = 0, cin >> Q[i].r >> Q[i].k, D[++len] = Q[i].k;
    }
    sort(D + 1, D + 1 + len), len = unique(D + 1, D + 1 + len) - D - 1;
    b1 = sqrt(n), b2 = sqrt(len);
    FL(i, 1, n)id1[i] = (i - 1) / b1 + 1, a[i] = lower_bound(D + 1, D + 1 + len, a[i]) - D;
    FL(i, 1, len)id2[i] = (i - 1) / b2 + 1;
    FL(i, 1, id1[n]){
        L[i] = R[i - 1] + 1, R[i] = min(L[i] + b1 - 1, n);
        FL(j, 1, id2[len])s1[i][j] = s1[i - 1][j];
        FL(j, 1, len)s2[i][j] = s2[i - 1][j];
        FL(j, L[i], R[i])s1[i][id2[a[j]]]++, s2[i][a[j]]++;
    }
    FL(i, 1, m){
        if(Q[i].l)cout << D[query(Q[i].l, Q[i].r, Q[i].k)] << endl;
        else modify(Q[i].r, lower_bound(D + 1, D + 1 + len, Q[i].k) - D);
    }
    return 0;
}

整体二分

将同一个范围内的答案统一处理,将多个询问一起解决。
利用分治将问题转化成判定,并不断地将答案取值区间二分。
然后把询问分成两部分,然后在递归至底部后获得一批询问的答案。

代码
#include<bits/stdc++.h>
using namespace std;
#define endl '\n'
#define FL(a,b,c) for(int a=(b),a##end=(c);a<=a##end;++a)
#define FR(a,b,c) for(int a=(b),a##end=(c);a>=a##end;--a)
#define lowbit(x) ((x)&-(x))
#define eb emplace_back
#define sz(x) (int)((x).size())
#define int long long
#define vt vector
#define fr first
#define se second
#define mmt(x, y) memset(x, y, sizeof x)
#define PII pair<int, int>
#define max(a, b)({auto f7r=(a);auto j3h=(b);f7r<j3h?j3h:f7r;})
#define cmax(a, b)({auto j3h=(b);(j3h>a)&&(a=j3h);})
#define min(a, b)({auto f7r=(a);auto j3h=(b);f7r>j3h?j3h:f7r;})
#define cmin(a, b)({auto j3h=(b);(j3h<a)&&(a=j3h);})
constexpr int N = 5e5 + 10;
struct A{int op, id, k, l, r;}v[N], P[N], Q[N];
int n, m, cnt, D[N], len, a[N], ans[N], w[N];
void add(int x, int y){while(x <= len)w[x] += y, x += lowbit(x);}
int query(int x){int ans = 0;while(x)ans += w[x], x -= lowbit(x);return ans;}
void solve(int l, int r, int L, int R){
	if(L == R){
		FL(i, l, r)if(!v[i].op)ans[v[i].id] = L;
		return;
	}
	int mid = L + R >> 1, p = 0, q = 0;
	FL(i, l, r)
		if(v[i].op){
			if(v[i].k <= mid)add(v[i].id, v[i].op), P[++p] = v[i];
			else Q[++q] = v[i];
		}
		else{
			int cnt = query(v[i].r) - query(v[i].l - 1);
			if(cnt >= v[i].k)P[++p] = v[i];
			else Q[++q] = v[i], Q[q].k -= cnt;
		}
	FL(i, l, r)if(v[i].op && v[i].k <= mid)add(v[i].id, -v[i].op);
	FL(i, 1, p)v[i + l - 1] = P[i];
	FL(i, 1, q)v[i + l + p - 1] = Q[i];
	if(p)solve(l, l + p - 1, L, mid);
	if(q)solve(r - q + 1, r, mid + 1, R);
}
int32_t main(){
	cin.tie(0)->sync_with_stdio(0);
	cin >> n >> m;
	int x;
	FL(i, 1, n)cin >> x, v[++cnt] = {1, i, x}, D[++len] = x, a[i] = x;
	FL(i, 1, m){
		char op;int x, y, z;
		cin >> op >> x >> y;
		if(op == 'Q')cin >> z, v[++cnt] = {0, i, z, x, y};
		else v[++cnt] = {-1, x, a[x]}, v[++cnt] = {1, x, (a[x] = y)}, D[++len] = y; 
	}
	sort(D + 1, D + 1 + len), len = unique(D + 1, D + 1 + len) - D - 1;
	FL(i, 1, cnt)if(v[i].op)v[i].k = lower_bound(D + 1, D + 1 + len, v[i].k) - D;
	solve(1, cnt, 1, len);
	FL(i, 1, m)if(ans[i])cout << D[ans[i]] << endl;
	return 0;
}

1.png

posted @ 2025-09-18 14:26  fush's_blog  阅读(8)  评论(0)    收藏  举报