POJ 1990 MooFest
题目链接: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;
}
稍微总结下吧:
不可能一拿到题目就想到是这种方法的
数据结构的题,一般是暴力搞不动,会超时
为了减小复杂度,来维护一个什么值,所以需要用线段树或者树状数组
树状数组好写,但是适用度比线段树要低