题目链接: MooFest
大致题意
现在告诉你奶牛 i
的听力为 vi
,这表示如果奶牛 j
想说点什么让她听到,必须用高于 vi×dis(i,j)
的音量。因此,如果奶牛 i
和 j
想相互交谈,她们的音量必须不小于 max(vi,vj)×dis(i,j)
。其中 dis(i,j)
表示她们间的距离。
现在 N
只奶牛都站在一条直线上了,每只奶牛还有一个坐标 xi
。如果每对奶牛都在交谈,并且使用最小音量,那所有 N(N−1)/2
对奶牛间谈话的音量之和为多少?
解题思路
线段树 本身这个题是树状数组专题的, 但是由于要维护两个信息, 还是习惯写线段树
我们首先考虑把所有奶牛按照他们的声音从小到大排序. 然后按照这样的顺序依次加入每个奶牛, 同时计算出他与已加入奶牛的音量贡献. 这样我们就可以在O(nlogn)的时间复杂度做出了.
考虑到我们需要维护的信息, 我们线段树应该维护的是坐标轴, 分别记录当前区间的奶牛个数, 以及距离O点(坐标为0)的距离和.
这样当我们当加入一个听力为val, 且位于index位置的奶牛时, 产生的贡献分为两种:
①所有已加入的奶牛, 位于其左侧, 则resleft = val * (left.num * index - left.sum).
②所有已加入的奶牛, 位于其右侧, 则resright = val * (right.sum - right.num * index);
最后结果变化为 resleft + resright.
AC代码
#define rep(i, n) for (int i = 1; i <= (n); ++i)
using namespace std;
typedef long long ll;
const int N = 2E4 + 10;
struct node {
int l, r;
ll sum; int num;
}t[N << 2];
void pushup(int x) {
t[x].sum = t[x << 1].sum + t[x << 1 | 1].sum;
t[x].num = t[x << 1].num + t[x << 1 | 1].num;
}
void build(int l, int r, int x = 1) {
t[x] = { l, r , 0, 0 };
if (l == r) return;
int mid = l + r >> 1;
build(l, mid, x << 1), build(mid + 1, r, x << 1 | 1);
}
void modify(int a, int c, int x = 1) {
if (t[x].l == t[x].r) {
t[x].num++, t[x].sum += c;
return;
}
int mid = t[x].l + t[x].r >> 1;
modify(a, c, x << 1 | (a > mid));
pushup(x);
}
node ask(int l, int r, int x = 1) {
if (l <= t[x].l and r >= t[x].r) return t[x];
int mid = t[x].l + t[x].r >> 1;
node left, right; left = right = { 0, 0, 0, 0 };
if (l <= mid) left = ask(l, r, x << 1);
if (r > mid) right = ask(l, r, x << 1 | 1);
return { 0, 0, left.sum + right.sum, left.num + right.num };
}
int main()
{
int n; cin >> n;
build(1, N - 5);
vector<pair<int, int> > v; // v, index
rep(i, n) {
int a, b; scanf("%d %d", &a, &b);
v.push_back({ a, b });
}
sort(v.begin(), v.end());
ll res = 0;
rep(i, v.size()) {
int a = v[i - 1].first, b = v[i - 1].second;
node left = ask(1, b), right = ask(b, N - 5);
res += a * (1ll * left.num * b - left.sum);
res += a * (right.sum - 1ll * right.num * b);
modify(b, b);
}
cout << res << endl;
return 0;
}