权值线段树,顾名思义,是一数的值的大小作为下标建立数段树的,所以通常需要先离散化再建树。个人感觉有点像主席树,只是不是可持久化的而已。
作用(个人观点):查询某个数的排名,有多少个数比它小,比它大,以及查询某个排名的数是多少。
写法:和普通线段一样,只是这里的节点是记录数据出现的次数而已;
以一个题目为例:
题意:给一个数列,对于每一个位置i,询问至少需要改变前面多少个数(不包括i这个位置),使得前缀和pre[i]<=m,改变某个数即是让它变为0;
思路:为了使我们要改变的数的数量最少,肯定优先改变最大的数。考虑用一个优先队列存储前面的数据,每次弹出最大的数之后,判断当前的前缀和是否满足条件。直到满足条件,题目保证对于每一个数w[i]<=m,所以这样是可行的。但是考虑时间复杂度,每次弹出,结束之后又得入队。那这样肯定爆炸了。
考虑用权值线段树维护前i-1个数据,我们很方便的能知道前k大的元素,现在让我们尝试二分需要改变的数的数量L=0,R=i-1;复杂度O(nlog^2n),tle;
想办法降一个logn,我们维护权值线段树的时候加一个区间值val维护,前缀后为pre[i],那么我们需要找到一些数的和x,满足
pre[i]-x<=m,即x>=pre[i]-m;
查询的时候优先查询右区间。若右区间得到值>=pre[i]-m,则直接往右区间查询即可,否则查询左区间+右区间的全部数量,(查询左区间记得减去右区间的值):
具体参看代码:
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int N = 1000*100*2 + 10;
typedef pair<int, int>PII;
ll n, m;
ll pre[N],a[N],b[N];
int sz,ans[N];
int getid(int x) {
return lower_bound(b + 1, b + 1 + sz,x)-b;
}
struct {
int num[N << 2]; //数据个数
ll val[N << 2]; //区间值
void down(int rt) {
num[rt] = num[rt << 1] + num[rt << 1 | 1];
val[rt] = val[rt << 1] + val[rt << 1 | 1];
}
void build(int rt, int l, int r) {
if (l == r) {
num[rt] = 0; val[rt] = 0;
return;
}
int mid = l + r >> 1;
build(rt << 1, l, mid);
build(rt << 1|1, mid+1,r);
down(rt);
}
void update(int rt, int l ,int r, int p) {
if (l == r) {
num[rt]++; val[rt] += b[l];
return;
}
int mid = l + r >> 1;
if (p <= mid)update(rt << 1, l, mid, p);
else update(rt << 1 | 1, mid + 1, r, p);
down(rt);
}
int query(int rt, int l, int r, ll ans) {
if (l == r) {
return (ans+b[l]-1)/b[l]; //ans/b[i]向上取整
}
int mid = l + r >> 1;
int flag = 0;
if (val[rt << 1 | 1]>ans)return query(rt<<1|1,mid+1,r,ans); //右区间的值值足够大,直接查询右区间
return num[rt << 1 | 1] + query(rt<<1,l,mid,ans-val[rt<<1|1]); //右区间的值不足以提供ans,需要加上左区间的部分值。
}
}Tree;
int main() {
int t; scanf("%d",&t);
while (t--) {
scanf("%lld%lld",&n,&m);
for (int i = 1; i <= n; i++)scanf("%lld", a + i),b[i]=a[i],pre[i]=pre[i-1]+a[i];
sort(b+1,b+1+n);
sz = unique(b + 1, b + 1 + n) - b-1; //离散化
Tree.build(1,1,sz);
for (int i = 1; i <= n; i++) {
if(i>1)ans[i]=Tree.query(1, 1, sz, pre[i]-m);
Tree.update(1,1,sz,getid(a[i])); //加上单点更新
}
for (int i = 1; i <= n; i++)printf("%d ",ans[i]);
puts("");
}
return 0;
}
好像树状数组+二分答案也能过,但是懒得写了。上一个我二分答案+权值线段树超时的代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int N = 1000*100*2 + 10;
typedef pair<int, int>PII;
ll n, m;
ll pre[N],a[N],b[N];
int sz,ans[N];
int getid(int x) {
return lower_bound(b + 1, b + 1 + sz,x)-b;
}
struct {
int num[N << 2];
void down(int rt) {
num[rt] = num[rt << 1] + num[rt << 1 | 1];
}
void build(int rt, int l, int r) {
if (l == r) {
num[rt] = 0;
return;
}
int mid = l + r >> 1;
build(rt << 1, l, mid);
build(rt << 1|1, mid+1,r);
down(rt);
}
void update(int rt, int l ,int r, int p) {
if (l == r) {
num[rt]++;
return;
}
int mid = l + r >> 1;
if (p <= mid)update(rt << 1, l, mid, p);
else update(rt << 1 | 1, mid + 1, r, p);
down(rt);
}
ll query(int rt, int l, int r, int k) {
if (l == r) {
return 1LL*k*b[l];
}
int mid = l + r >> 1;
ll ans = 0;
if(num[rt<<1|1]>0)ans += query(rt<<1|1,mid+1,r,min(k,num[rt<<1|1]));
if (k>num[rt<<1|1])ans+=query(rt<<1,l,mid,k-num[rt<<1|1]);
return ans;
}
}Tree;
int main() {
int t; scanf("%d",&t);
while (t--) {
scanf("%lld%lld",&n,&m);
for (int i = 1; i <= n; i++)scanf("%lld", a + i),b[i]=a[i],pre[i]=pre[i-1]+a[i];
sort(b+1,b+1+n);
sz = unique(b + 1, b + 1 + n) - b-1;
Tree.build(1,1,sz);
for (int i = 1; i <= n; i++) {
int R = i - 1, L = 0;
while (R > L) {
int mid = L + R >> 1;
ll ss = Tree.query(1, 1, sz, mid);
if (pre[i]-ss<=m)R = mid;
else L = L + 1;
}
Tree.update(1,1,sz,getid(a[i]));
ans[i] = R;
}
for (int i = 1; i <= n; i++)printf("%d ",ans[i]);
puts("");
}
return 0;
}