题意:有n头牛,每头牛有两个属性,音量v,在X轴上的坐标x。 牛 i 牛_i 牛i和 牛 j 牛_j 牛j想要通话,音量至少是 m a x ( v i , v j ) ∗ a b s ( x i − x j ) max(v_i, v_j) * abs(x_i - x_j) max(vi,vj)∗abs(xi−xj),要求每对牛通话音量的和。
思路:
暴力做法就是 ∑ i = 0 n ∑ j = 0 i − 1 ( m a x ( v i , v j ) ∗ a b s ( x i − x j ) ) \sum_{i=0}^n\sum_{j=0}^{i-1}(max(v_i, v_j) * abs(x_i - x_j)) ∑i=0n∑j=0i−1(max(vi,vj)∗abs(xi−xj)),复杂度 O ( n 2 ) O(n^2) O(n2)。
我们按每头牛的 v i v_i vi排序,可以把上面的式子化简为 ∑ i n ∑ j i − 1 ( v i ∗ a b s ( x i − x j ) ) \sum_i^n\sum_j^{i-1}(v_i * abs(x_i - x_j)) ∑in∑ji−1(vi∗abs(xi−xj))
我们知道第 i i i头牛有 i − 1 i-1 i−1头牛比它的音量小,在把这 i − 1 i-1 i−1头牛分在这头牛 x i x_i xi的左边和右边,把绝对值 a b s abs abs给去掉,设左边牛的 ∑ x j \sum x_j ∑xj为 s l _ d i s sl\_dis sl_dis,总头数为 l _ n u m l\_num l_num;右边牛的 ∑ x j \sum x_j ∑xj为 s l _ d i s sl\_dis sl_dis,总头数为 r _ n u m r\_num r_num; i − 1 i-1 i−1头牛的总和为 s _ d i s s\_dis s_dis,总头数为 n u m = i − 1 num = i-1 num=i−1。
就把公式变成了 ∑ i = 1 n v i ∗ ( l _ n u m ∗ x i − s l _ d i s + s r _ d i s − r _ n u m ∗ x i ) \sum_{i=1}^nv_i *(l\_num*x_i -sl\_dis+sr\_dis-r\_num*x_i) ∑i=1nvi∗(l_num∗xi−sl_dis+sr_dis−r_num∗xi)。
中间求 s l _ d i s sl\_dis sl_dis, s r _ d i s sr\_dis sr_dis, s _ d i s s\_dis s_dis, l _ n u m l\_num l_num, r _ n u m r\_num r_num, n u m num num。就要用到树状数组,添加和查询每次操作都是 l o g ( n ) log(n) log(n)复杂度。所以总复杂度就是 O ( n l o g ( n ) ) O(nlog(n)) O(nlog(n))。
在这题中构建了两个树状数组,一个记录了牛的距离,一个记录了牛的头数。
因为是边查询边构树,保证了:在第 i i i头牛查询距离和头数时树状数组中都是 v v v小于 v i v_i vi的牛的和。
其中数组下标表示牛在X轴上的坐标。
#include<iostream>
#include<cstring>
#include<cstdio>
#include<queue>
#include<algorithm>
using namespace std;
typedef long long ll;
const ll N = 2e4+10;
ll tree[2][N];//1距离,0个数。
//每头牛的音量和距离,可以按音量大小从小到大排序
struct cow {
ll v, x;
inline bool operator < (cow U) const {
return v < U.v;
}
} C[N];
//对树状数组添加元素,每次都会在坐标x_i添加坐标和头数。
void add(ll p, ll val) {
while(p <= N) {
tree[1][p] += val;
tree[0][p] += 1;
p += (p & (-p));
}
}
//找出在该坐标下(左边的坐标总和)和(左边牛的头数总和)
ll query(ll p, ll k) {
ll ans = 0;
while(p) {
ans += tree[k][p];
p -= (p & (-p));
}
return ans;
}
int main() {
// freopen("in.txt", "r", stdin);
// freopen("out.txt", "w", stdout);
ll n, l_num, num, r_num, sr_dis, sl_dis, s_dis, ans = 0;
scanf("%lld", &n);
for(int i=0; i<n; i++) {
scanf("%lld%lld", &C[i].v, &C[i].x);
}
sort(C, C+n);
for(int i=0; i<n; i++) {
//从0开始,和上面有点偏差。
num = i;//小于v_i的牛的总头数
l_num = query(C[i].x, 0);//x_i左边的牛的头数
r_num = num - l_num;//x_i右边的牛的头数
s_dis = query(20000, 1);//牛的坐标总和
sl_dis = query(C[i].x, 1);//x_i左边牛的坐标总和
sr_dis = s_dis - sl_dis;//x_i右边牛的坐标总和
ans += (C[i].x * l_num - sl_dis + sr_dis - C[i].x * r_num) * C[i].v;
add(C[i].x, C[i].x);//添加元素,维护坐标和与牛的头数和。
}
printf("%lld", ans);
return 0;
}