权值线段树(HDU 6609)

权值线段树,顾名思义,是一数的值的大小作为下标建立数段树的,所以通常需要先离散化再建树。个人感觉有点像主席树,只是不是可持久化的而已。

作用(个人观点):查询某个数的排名,有多少个数比它小,比它大,以及查询某个排名的数是多少。

写法:和普通线段一样,只是这里的节点是记录数据出现的次数而已;

以一个题目为例:

点这里查看题目

题意:给一个数列,对于每一个位置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;
}
发布了70 篇原创文章 · 获赞 5 · 访问量 7186

猜你喜欢

转载自blog.csdn.net/xiaonanxinyi/article/details/97796224