题目链接:POJ1990

真的要下定决心来学习线段树和树状数组了,每次补题看到的题解都是基本题,水题,树状数组维护一下

不知道该吐槽自己弱,还是吐槽题解


题意:给n头牛的声调和位置,任意两头牛的交流花费值是距离*max(v【i】,v【j】)

求所有牛对的交流值之和


不可能暴力解的,那么需要高效的维护值,就得用数据结构来做

维护什么?维护位置,维护声调(不要觉得我废话)


方法1:按照v值升序排序(那么,不需要判断v【i】和v【j】的大小了,后插入的就是需要乘的值)

那么,需要快速求:对于牛i来说,x位置比i小的个数,以及这个牛前面的位置之和,这个牛后面的位置之和(输入按照牛i的编号来插入进树里就好)

所以需要两个树状数组

维护个数:一个记录比x小的牛的个数a

维护距离:在左边的距离之和:a*x【i】-b

(在右边)比牛i位置大的牛到牛i的距离之和:所有距离-b-(i-a-1)*x【i】

代码如下:

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cstdlib>
using namespace std;

const int MAX=2*1e4;
const int maxn=5*1e4+50;
int n;
struct Node{
	int v,x;
}a[maxn];
long long num[maxn];
long long dis[maxn];

int cmp(Node x,Node y){
	return x.v<y.v;
}

int lowbit(int x){
	return x&(-x);
}

void updatenum(int pos,int add){
	while(pos<=MAX){
		num[pos]+=add;
		pos+=lowbit(pos);
	}
}

void updatedis(int pos,int add){
	while(pos<=MAX){
		dis[pos]+=add;
		pos+=lowbit(pos);
	}
}

int getnum(int pos){
	long long ans=0;
	while(pos>0){
		ans+=num[pos];
		pos-=lowbit(pos);
	}
	return ans;
}

int getdis(int pos){
	long long ans=0;
	while(pos>0){
		ans+=dis[pos];
		pos-=lowbit(pos);
	}
	return ans;
}

int main(){
	//freopen("input.txt","r",stdin);
	while(scanf("%d",&n)!=EOF){
		memset(num,0,sizeof(num));
		memset(dis,0,sizeof(dis));
		for(int i=1;i<=n;i++) scanf("%lld%lld",&a[i].v,&a[i].x);
		sort(a+1,a+n+1,cmp);
		long long ans=0;
		for(int i=1;i<=n;i++){
			long long numi=getnum(a[i].x);
			long long disi=getdis(a[i].x);
			ans+=(a[i].x*numi-disi+getdis(MAX)-disi-(i-1-numi)*a[i].x)*a[i].v;
			updatenum(a[i].x,1);
			updatedis(a[i].x,a[i].x);
		}
		printf("%lld\n",ans);
	}
	return 0;
}



方法2:按照x值升序排序

还是维护个数和距离,在实现代码上,在main中的求解方法不同,思路一样

而且为了考虑到重复的音调,需要正向计算一次,反向计算一次


代码如下:(相同的部分不贴了,只贴了main函数)

int cmp(Node x,Node y){
	return x.x<y.x;
}

int main(){
	//freopen("input.txt","r",stdin);
	while(scanf("%d",&n)!=EOF){
		for(int i=1;i<=n;i++) scanf("%lld%lld",&a[i].v,&a[i].x);
		sort(a+1,a+n+1,cmp);
		long long ans=0,numi,disi;
		memset(num,0,sizeof(num));
		memset(dis,0,sizeof(dis));
		for(int i=1;i<=n;i++){
			numi=getnum(a[i].v-1);
			disi=getdis(a[i].v-1);
			ans+=(numi*a[i].x-disi)*a[i].v;
			updatenum(a[i].v,1);
			updatedis(a[i].v,a[i].x);
		}
		memset(num,0,sizeof(num));
		memset(dis,0,sizeof(dis));
		for(int i=n;i>=1;i--){
			numi=getnum(a[i].v);
			disi=getdis(a[i].v);
			ans+=(disi-numi*a[i].x)*a[i].v;
			updatenum(a[i].v,1);
			updatedis(a[i].v,a[i].x);
		}
		printf("%lld\n",ans);
	}
	return 0;
}

稍微总结下吧:

不可能一拿到题目就想到是这种方法的

数据结构的题,一般是暴力搞不动,会超时

为了减小复杂度,来维护一个什么值,所以需要用线段树或者树状数组

树状数组好写,但是适用度比线段树要低