You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

123 lines
3.7 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

## [$P2345$ 奶牛集会](https://www.luogu.org/problemnew/show/P2345)
### 一、题目描述
约翰的$N$头奶牛每年都会参加 **哞哞大会**。哞哞大会是奶牛界的盛事。集会上的活动很多,比如堆干草,跨栅栏,摸牛仔的屁股等等。它们参加活动时会聚在一起,第$i$头奶牛的坐标为$X_i$,没有两头奶牛的坐标是相同的。奶牛们的叫声很大,第$i$头和第$j$头奶牛交流,会发出$max(V_i,V_j) × |X_iX_j|$ 的音量,其中$V_i$ 和$V_j$ 分别是第$i$ 头和第$j$,头奶牛的听力。假设每对奶牛之间同时都在说话,请计算所有奶牛产生的音量之和是多少。
$Input$
第一行:单个整数$N$
第二行到第$N+1$ 行:第$i+1$ 行有两个整数$V_i$和$X_i$。
$Output$
单个整数:表示所有奶牛产生的音量之和
$Sample$ $Input$
```cpp {.line-numbers}
4
3 1
2 5
2 6
4 3
```
$Sample$ $Output$
```cpp {.line-numbers}
57
```
$Hint$
所有数据≤ $20000$
### 二、解题思路
首先分析一下式子$max\{V_i,V_j\}×|X_i X_j|$
想办法化简公式,去掉原公式中的$max$和绝对值符号。
1. 将每头奶牛按听力$V_i$由小到大进行排序,那么当走到第$i$头牛时,它与前$i-1$头牛的$\displaystyle \large \max_{j<i}(V_i,V_j)=V_i$,这样就成功去掉了最左侧的$max\{V_i,V_j\}$
2. $|X_i X_j|$这个分两种情况:
- $X_i>X_j$,转换为$X_i-X_j$
- $X_i<X_j$,转换为$X_j-X_i$
3. $\large \displaystyle \sum max\{V_i,V_j\}×|X_i X_j|$
- 第$i$头牛的$v_i$ * (它之前牛的个数 * 它的坐标 - 它之前所有牛的坐标和)
- 第$i$头牛的$v_i$ * (它之后所有牛的坐标和 - 它之后牛的个数 * 它的坐标)
第$i$头牛和其他的牛发出的声音 = +
对于牛在某个坐标区间内的个数和牛的坐标和,我们可以用两个树状数组来维护。
### 三、$Code$
```cpp {.line-numbers}
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 20010;
// 听力值v,坐标x
// 结构体+第一维、第二维由小到大排序
struct Node {
int v, x;
const bool operator<(const Node &t) const {
if (v == t.v) return x < t.x;
return v < t.v;
}
} a[N];
// 树状数组模板
int c1[N], c2[N];
#define lowbit(x) (x & -x)
void add(int c[], int x, int v) {
while (x < N) c[x] += v, x += lowbit(x);
}
int sum(int c[], int x) {
int res = 0;
while (x) res += c[x], x -= lowbit(x);
return res;
}
int main() {
#ifndef ONLINE_JUDGE
freopen("P2345.in", "r", stdin);
#endif
int n;
scanf("%d", &n);
for (int i = 1; i <= n; i++) scanf("%d %d", &a[i].v, &a[i].x);
sort(a + 1, a + n + 1); // 排序
LL res = 0;
for (int i = 1; i <= n; i++) {
// c1:坐标和树状数组
// c2:牛的个数
LL s1 = sum(c1, a[i].x - 1); // a[i]进入树状数组时,它前面所有牛的坐标和
LL s2 = sum(c1, 20000) - sum(c1, a[i].x); // a[i]进入树状数组时,它后面所有牛的坐标和
LL cnt = sum(c2, a[i].x);
/*
cnt:它之前牛的个数
i-1-cnt:它之后牛的个数
a[i].x:它的坐标
*/
// 方法1cout << "i=" << i << ",sum(c2,N)=" << sum(c2, N) << endl;
res += a[i].v * (cnt * a[i].x - s1 + s2 - (sum(c2, N) - cnt) * a[i].x);
// 方法2
// res += a[i].v * (cnt * a[i].x - s1 + s2 - (i - 1 - cnt) * a[i].x);
add(c1, a[i].x, a[i].x); // 维护坐标前缀和
add(c2, a[i].x, 1); // 维护个数前缀和
}
// 输出结果
printf("%lld\n", res);
return 0;
}
```