Luogu P2345 奶牛集会
这道题可以用分治的方法解决。(lbgxld说是线段树,但是我觉得分治快而且好写...)
暴力枚举是$O(n^2)$,想要优化,就不能一对一对枚举,最好能用一只奶牛一次计算一群其他的贡献。
这就需要考虑听力$v$和坐标大小$x$的关系,可以把它转化为一个二维偏序问题。
首先把听力$v$从大到小排序,控制一维。
然后用归并排序,按$x$从小到大排序。
因为左边一半$(l,mid)$奶牛的$v$一定大于右边的$(mid+1,r)$,那么我们只计算左边的每一个对右边的贡献。
也就是说,只有当左边的一个奶牛加入归并的数组时,才分别统计坐标比它小和比它大的贡献。
对于左边的一个奶牛,设它的听力为$vi$,坐标为$xi$。
设$s1$为右边所有$x$比$xi$小的的坐标之和 (初始为0);
设$s2$为右边所有$x$比$xi$大的的坐标之和 (初始为$(mid+1,r)$的奶牛坐标之和)。
归并排序的过程,就是不断选取$x$较小的奶牛加入归并数组中。
如果枚举到右边的一个奶牛,不统计贡献;但要把$s1$加上它的$x$,$s2$减去它的$x$。
如果枚举到左边的一个奶牛,统计贡献:
已经统计出了有几只奶牛的坐标比$xi$小,而剩余的比$xi$大,
要求坐标差的绝对值,答案即为$[ (xi*比它小的个数-s1)+(s2-xi*比它大的个数) ]* vi$(因为这个$vi$一定大于所有右边的)。
因为两边已经按$x$从小到大排好,所以左边在$xi$之后的奶牛的坐标一定比$xi$大,也一定能对已经被加入$s1$中的奶牛做出贡献。
一开始我想直接按二维偏序的方法做,只记录比当前坐标小的奶牛的贡献,也就是只用大的减小的,最后乘2。
但是这道题并不是严格的偏序问题,它除了求逆序对还要求顺序对,这两个数量之和显然不是逆序对数量*2。
代码如下
#include<cstdio>
#include<iostream>
#include<cmath>
#include<cstring>
#define MogeKo qwq
#include<algorithm>
#define int long long
using namespace std;
const int maxn = 1e6;
int n,ans;
struct vx {
int v,x;
bool operator < (const vx & N) const {
return v>N.v || ( v==N.v && x<N.x);
}
} a[maxn],b[maxn];
void cdq(int l,int r) {
if(l==r)return;
int mid = (l+r)>>1;
cdq(l,mid);
cdq(mid+1,r);
int s1 = 0,s2 = 0;
for(int i = mid+1;i <= r;i++)
s2 += a[i].x;
int i = l,j = mid+1,k = l;
while(i <= mid) {
while(j <= r && a[i].x > a[j].x) {
s1 += a[j].x;
s2 -= a[j].x;
b[k++] = a[j++];
}
ans += a[i].v*((j-mid-1)*a[i].x - s1 + s2 - (r-j+1)*a[i].x);
b[k++] = a[i++];
}
while(j <= r)
b[k++] = a[j++];
for(int i = l; i <= r; i++)
a[i] = b[i];
}
main() {
scanf("%lld",&n);
for(int i = 1; i <= n; i++)
scanf("%lld%lld",&a[i].v,&a[i].x);
sort(a+1,a+n+1);
cdq(1,n);
printf("%lld\n",ans);
}