[USACO04OPEN]MooFest
题目
Description
约翰的N 头奶牛每年都会参加“哞哞大会”。哞哞大会是奶牛界的盛事。集会上的活动很多,比如堆干草,跨栅栏,摸牛仔的屁股等等。它们参加活动时会聚在一起,第i 头奶牛的坐标为Xi,没有两头奶牛的坐标是相同的。奶牛们的叫声很大,第i 头和第j 头奶牛交流,会发出max{Vi; Vj}×|Xi − Xj | 的音量,其中Vi 和Vj 分别是第i 头和第j 头奶牛的听力。
假设每对奶牛之间同时都在说话,请计算所有奶牛产生的音量之和是多少。
Input
• 第一行:单个整数N,1 ≤ N ≤ 20000
• 第二行到第N + 1 行:第i + 1 行有两个整数Vi 和Xi,1 ≤ Vi ≤ 20000; 1 ≤ Xi ≤ 20000
Output
• 单个整数:表示所有奶牛产生的音量之和
Sample Input
4 3 1 2 5 2 6 4 3
Sample Output
57
思路
首先肯定想到按坐标先排序;
我们可以枚举每个点假设作为 $max(v[i],v[j])$ 较大的那一个;
然后用树状数组分别找出前面 $\leq v[i]$ 的牛 和 后面 $\leq v[i]$ 小的牛;
分别加上音量即可;
那么求距离需要开两个树状数组,一个存 $\leq v[i]$ 的个数,另一个存 $\leq v[i]$ 的坐标和;
$i$ 与所有点的距离和就是 ,个数$\times v[i]$ $-$ 坐标和 ;
注意这里有个魔鬼细节:
可能会出现了重复的 $v[i]$ ;
假设两个点坐标是 $i$ ,$j$ $( i \leq j)$;
这样可能导致找前面 $\leq v[j]$ 时计算了一遍了 $v[i]$ ; 找后面 $\leq v[i]$ 时计算了一遍 $v[j]$ ;
那么解决的话,就在找前面 或 找后面时 改成求 $<v[i]$ 就可以了;
写完后,我看了一下其他巨佬的题解;
写法不太一样,瞬间觉得我的写法弱爆了;
代码
#include<bits/stdc++.h> #define re register typedef long long ll; using namespace std; inline ll read() { ll a=0,f=1; char c=getchar(); while (c<'0'||c>'9') {if (c=='-') f=-1; c=getchar();} while (c>='0'&&c<='9') {a=a*10+c-'0'; c=getchar();} return a*f; } ll n; ll mx=-1; struct ljj { ll x,v; }a[50010]; struct ljq { ll w,num; }sum[50010]; inline ll lowbit(ll x) { return x&(-x); } inline void insert(ll x,ll y) { while(x<=mx) { sum[x].w+=y; x+=lowbit(x); } } inline ll findout(ll x) { ll s=0; while(x) { s+=sum[x].w; x-=lowbit(x); } return s; } inline void add(ll x,ll y) { while(x<=mx) { sum[x].num+=y; x+=lowbit(x); } } inline ll find(ll x) { ll s=0; while(x) { s+=sum[x].num; x-=lowbit(x); } return s; } inline ll cmp(ljj a,ljj b) { if(a.x==b.x) return a.v<b.v; else return a.x<b.x; } int main() { n=read(); for(re ll i=1;i<=n;i++) { a[i].v=read(); a[i].x=read(); mx=max(a[i].v,mx); } sort(a+1,a+n+1,cmp);//按坐标排序 ll ans=0; for(re ll i=1;i<=n;i++) { ll sum=findout(a[i].v);//计算前面小于等于 v[i] 的坐标和 ll t=find(a[i].v); //找小于等于 v[i] 的个数 insert(a[i].v,a[i].x); add(a[i].v,1); ans+=abs(t*a[i].x-sum)*a[i].v; //所有坐标相减 乘以 v[i] } memset(sum,0,sizeof(sum)); for(re ll i=n;i>=1;i--) { ll sum=findout(a[i].v-1);//计算后面小于 v[i] 的坐标和 ll t=find(a[i].v-1); //找小于 v[i] 的个数 insert(a[i].v,a[i].x); add(a[i].v,1); ans+=abs(t*a[i].x-sum)*a[i].v; //所有坐标相减 乘以 v[i] } printf("%lld\n",ans); //return 0; }