事实证明真的不能一个下午连续调一道题 (还没调出来) ,搞得虽然去骑车吃饭转了一圈,晚上回来图书馆脑子还是晕晕的,最后只做了一道半题。
言归正传,原题:[www.luogu.com.cn/problem/P50…]
首先肯定要想想怎么把那么多的数整理一下,让它看起来有序一点。注意到对于任意的 ,我们都是取 ,所以考虑一下能不能通过这个最大值做一下文章。我们不妨将所有奶牛按照 的值从小到大排序,然后记 为奶牛 和奶牛 之间的距离,然后对于排序后的第 头奶牛,我们可以发现,对于排在它前面的所有奶牛 ,奶牛 和奶牛 之间的交流都是取的 为音量,所以对第 头奶牛,由它的听力所决定的音量和为:
那么我们该怎么处理第 头奶牛与前面的所有 头奶牛的距离呢?我们不妨分类讨论一下。
可知当前第 头奶牛的位置为 ,则 ,有 。设在这 头奶牛中有 头奶牛满足上述条件,则这些奶牛和第 头奶牛的总音量值为
同样的, ,有 。设在这 头奶牛中有 头奶牛满足上述条件,则这些奶牛和第 头奶牛的总音量值为
那么如何维护第 头奶牛前面的奶牛的位置呢?我们可以使用树状数组。
我们开两个树状数组,一个用于维护位置的和,一个用于维护相应位置上出现的频率。
具体的,我们先按 值排序,然后动态向两个树状数组中加入对应的点。设当前奶牛的位置为 ,则我们给第一个树状数组的 位置加上 ,给第二个树状数组的 位置加上 。然后每次计算当前位置的贡献时,就取得在 左边的位置的和以及在右边的位置的和,还有两边的频率计数,最终按公式直接计算即可。
coding:
cin >> n;
vector<cow> a(n + 1);
for (int i = 1; i <= n; i++)
{
cin >> a[i].v >> a[i].x;
max_pos = max(max_pos, a[i].x);
}
sort(a.begin() + 1, a.end(), [](auto &a, auto &b)
{ return a.v < b.v; });
ll ans = 0;
fenwick_tree ft(max_pos), cnt(max_pos);
for (int i = 1; i <= n; i++)
{
ll now_pos = a[i].x, now_val = a[i].v;
ll less_cnt = cnt.query(now_pos - 1), greater_cnt = cnt.query_range(now_pos + 1, max_pos);
ll less_sum = ft.query(now_pos - 1), greater_sum = ft.query_range(now_pos + 1, max_pos);
ans += now_val * (less_cnt * now_pos - less_sum + greater_sum - greater_cnt * now_pos);
ft.update(now_pos, now_pos);
cnt.update(now_pos, 1);
}
cout << ans;
总结一下,总体时间复杂度为 ,主要在排序和每次树状数组的询问和更新上。
为什么使用树状数组?而不是线段树或者前缀和?首先前缀和难以实现动态更新,只能静态查询,在这种环境中就直接被pass掉了。至于线段树,相对来说码量较大,实现较为复杂,且在这种特定的单点更新以及前缀查询环境中的表现没有树状数组好,因为它的常数是比较大的。
为什么会想到使用树状数组?因为我们需要查询特定区间和,用于求解问题。