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
",ans);
}
View Code

 

原文地址:https://www.cnblogs.com/mogeko/p/10943622.html