树状数组之单点修改并求全局第K小
树状数组模板如下
可实现建树、查询、倍增求第K小
class Fenw {
public:
int n;
vector<int> c;
Fenw(int size) {
n = size;
c.resize(n+1, 0);
}
void add(int i, int x) {
while (i <= n) {
c[i] += x;
i += i & -i;
}
}
int query(int i) {
if (i <= 0) return 0;
int s = 0;
while (i) {
s += c[i];
i -= i & -i;
}
return s;
}
int kth(int k) {
int idx = 0;
for (int i = 21; i >= 0; i--) {
int nxt = idx + (1 << i);
if (nxt > n) continue;
if (c[nxt] < k) {
k -= c[nxt];
idx = nxt;
}
}
return idx + 1;
}
};
小知识:权值数组
一个序列 a 的权值数组 b,满足 b[x] 的值为 x 在 a 中的出现次数。
例如:a = (1, 3, 4, 3, 4) 的权值数组为 b = (1, 0, 2, 2)。
很明显,b 的大小和 a 的值域有关。
若原数列值域过大,且重要的不是具体值而是值与值之间的相对大小关系,常 离散化 原数组后再建立权值数组。
另外,权值数组是原数组无序性的一种表示:它重点描述数组的元素内容,忽略了数组的顺序,若两数组只是顺序不同,所含内容一致,则它们的权值数组相同。
对于数据范围过大时,需要考虑离散化,并加入树状数组中。具体操作为:
1.将原数组离散化,并把所有操作离线后加入离散化的权值数组
2.对于原数组的单点修改,如$a[x]$a[x] 从$y$ 修改为 $z$,转化为对权值数组 $b$ 的单点修改就是 $b[y]$ 单点减 $1$,$b[z]$ 单点加 $1$。
3.对于查询第 $k$ 小,考虑二分 $x$,查询权值数组中 $[1, x]$ 的前缀和,找到 $x_0$ 使得 $[1, x_0]$ 的前缀和 $< k$ 而 $[1, x_0 + 1]$ 的前缀和 $\ge k$,则第$ k$ 大的数是 $x_0 + 1$(注:这里认为 $[1, 0]$ 的前缀和是 $0$)。
e.g.
cin>>n>>q;
vector<int> a(n+1);
for(int i=1;i<=n;i++){
cin>>a[i];
}
vector<pii> op(q+1);
vector<int> cp=a;
vector<int> all=a;
for(int i=1;i<=q;i++){
int x,y;cin>>x>>y;
op[i]={x,y};
cp[x]+=y;
all.push_back(cp[x]);
}
sort(all.begin()+1,all.end());
all.erase(unique(all.begin(),all.end()),all.end()); //注意去重
auto get=[&](int x){
return lower_bound(all.begin(),all.end(),x)-all.begin();
};
Fenw Fenw((int)all.size());
cp=a;
//建树,此时将all数组离散化,存储的是某个值出现的位置
for(int i=1;i<=n;i++){
int idx=get(cp[i]);
Fenw.add(idx+1,1);
}
int K=(n+1)/2;
vector<int> ans;
for(int i=1;i<=q;i++){
int fi=op[i].first,se=op[i].second;
//将对原数组的修改体现在树状数组中
int x=cp[fi];
int y=get(x);
Fenw.add(y+1,-1);
int tx=x+se;
cp[fi]=tx;
int ty=get(tx);
Fenw.add(ty+1,1);
//查询在K+1位置下的值
int cnt=Fenw.kth(K+1);
//查询比cnt小的位置有多少个值
ans.push_back(Fenw.query(cnt-1));
}
浙公网安备 33010602011771号