金色丝线将瞬间一分为二 树状数组的使用

Description
为了解开骑士木乃伊事件,久城一弥和布洛瓦警官来到了停尸间。停尸间里有N具遗体,每具遗体都有一个坐标(X,Y)。
由于停尸间的遗体摆放得横平竖直,我们认为两具遗体(Xi,Yi)和(Xj,Yj)得距离为|Xi-Xj|+|Yi-Yj|。
负责停尸间的工人由于需要经常搬运遗体,所以对任意两具遗体的距离之和特别有印象。
工人们已经记不得每具遗体对应的是什么人了。但是它们记得,八年前将米莉·马露的遗体搬进停尸间之后,停尸间的任意两具遗体的距离之和超过了D。
现在给你工人们将N具遗体搬进停尸间的时间顺序,请你找出第一具有可能是米莉·马露的遗体。如果不存在这样的遗体,请输出-1。

Input
第一行两个整数N,D,意义如题目描述所示。
接下来N行,按照时间顺序给出每具遗体的坐标,每行两个整数X,Y。

Output
输出一行一个整数,表示按照时间顺序第一具有可能是米莉·马露的遗体的编号。如果不存在这样的遗体,输出-1。
Sample Input
5 10
1 1
2 2
3 3
4 4
5 5
Sample Output
4
Data Constraint
10%的数据保证,N≤500.
40%的数据保证,N≤8000.
60%的数据保证,N≤1e5.
另有10%的数据保证,所有遗体的横坐标X都相同。
另有10%的数据保证,X(i)≤X(i+1),且Y(i)≤Y(i+1)。
100%的数据保证,N≤6×10^5,0≤D≤1e18,0≤X,Y≤1e9.

观察题目,我们可以知道,题目求的是对于任意的k,使得Σ(|xi-xj|+|yi-yj|)>D (1≤i<j≤k),最小的k是多少。
理解题目,可以很容易的知道,若我们前n-1具尸体间的曼哈顿距离之和为f(n-1),则f(n)=f(n-1)+Σ(|xi-xn|+|yi-yn|) (1≤i≤n-1)
因此,我们可以知道,对于这样的函数f(x)必然是一个单调递增的函数。
我们重新回头观察题目,对于两个点(xi,yi),(xj,yj),曼哈顿距离 distance=|xi-xj|+|yi-yj|,那么,我们可以变式为 distance=max(xi,xj)-min(xi,xj)+max(yi,yj)-min(yi,yj)。
而我们再移动一下式子,让它没有负号,并消掉一边的函数符号,过程如下
distance+min(xi,xj)+min(yi,yj)=max(xi,xj)+max(yi,yj)→distance+2×min(xi,xj)+2×min(yi,yj)=max(xi,xj)+min(xi,xj)+max(yi,yj)+min(yi,yj)
经过刚才的过程,我们得到了式子 distance=xi+xj+yi+yj-2×min(xi,xj)-2×min(yi,yj)。
当然,这个式子我们也可以理解成点i,点j以点(0,0)为中转站,而得到的它们间的曼哈顿距离。
所以,我们就可以得到函数 dis(i,j)=xi+xj+yi+yj-2×min(xi,xj)-2×min(yi,yj)。
使用刚才得到的函数,我们可以将前k个点的任意两具遗体的距离的和表示成 Σdis(i,j) (1≤i<j≤k)。
我们将刚才的函数分成正的和负的两部分,可以很快发现,我们可以很快的处理出正的部分,例如,对于前k个点,我们在记录下sum=Σ(xi+yi) (1≤i≤k),因为每个点都会和其它(k-1)个点求取距离,所以正的部分应该是 sum×(k-1)。
而负的部分,我们则需进行一下讨论。
我们留意一下,可以发现,刚才的函数中,实际上,横坐标和纵坐标是分离的,也就是说,它们并不需要一一对应,(xi,yi)与(xj,yj)的距离和(xi,yj)、(xj,yi)的距离是相同的。所以,我们分别研究它们的横坐标x和纵坐标y。
首先,我们考虑暴力。对于前k个点,我们可以对它们进行一次排序,然后,对于横坐标的排序中,点i的排名是ri,我们可以知道它的值xi仅仅对后面(k-ri)个点有贡献,前面的ri-1个点与它比较时,前面点的x值做贡献。那么,我们就可以知道,点i所能做的贡献时 xi×(k-ri),所以,我们可以得到前k个点所作贡献的大小 Σ[xi×(k-ri)] (1≤i≤k)
但是,该方法要不断重新统计任意两点间的曼哈顿距离之和,很显然方法效率并不高, 复杂度达到O(n^2),并且,还要不断维护一个有序的数列,因此,并不可取。
根据上面讲述过的,函数f(x)具有单调性,其值随x增大而增大。所以,我们可以考虑不断求取f(x)的变化值来得到答案。
例如,在正的部分,由于前k个数的和sum=Σ(xi+yi) (1≤i≤k),那么,(k-1)个数的和便是sum=Σ(xi+yi) (1≤i≤k-1).
因为,前k个数,正的部分的总和应该是(k-1)sum,也就是(k-1)Σ(xi+yi) (1≤i≤k) ,对于前k-1个数,整的部分的总和是 (k-2)Σ(xi+yi) (1≤i≤k-1) 。因此,我们做差,(k-1)Σ(xi+yi) (1≤i≤k)-(k-2)Σ(xi+yi) (1≤i≤k-1)=(k-1)Σ(xi+yi) (1≤i≤k-1)+(k-1)(xk+yk)-(k-2)Σ(xi+yi) (1≤i≤k-1)=Σ(xi+yi) (1≤i≤k-1)+(k-1)(xk+yk).
所以,从前(k-1)个数到前k个数,正的部分的增加的值,是Σ(xi+yi) (1≤i≤k-1)+(k-1)(xk+yk)。当然,纵坐标y的变化也是同理。
观察负的部分,我们也寻找它们的变化量。
根据之前的公式 dis(i,j)=xi+xj+yi+yj-2×min(xi,xj)-2×min(yi,yj),很容易想到,我们需要分类讨论。
易知,假如当前已经有一个有序的序列,里面有(k-1)个数。那么当我们插入一个数xk,它当前的排名是rk,那么,逐渐递增的序列中,(1~(rk-1))的数都会与xk这个数计算距离,并且,对答案做出贡献的是前(rk-1)个数。因为在这个序列中,前(rk-1)个数的大小小于xk,所以在取min时前面的数要做出贡献,而xk不作贡献。所以,此时负的部分会减少 2Σ(xi) (1≤i<k,ri<rk)
另外,在序列中,排名为(rk+1~k)的数,在与xk计算距离时,xk显然要小于后面的数,因此,xk要做出贡献。然后,xk产生的贡献,就是 2×xk×(当前有多少个数大于xk)。
由于负的部分中出现了两个统计,一个是统计有多少个数大于xk,另一个部分是统计小于xk的数的和,我们选择使用树状数组来解决问题。
当然,选择线段树解决问题,答案并不会错误,但是由于复杂度的原因,会出现超时的情况。
代码如下:

#include 

#define LL long long 

using namespace std;

const int maxn=600010;
LL ans,d,sum;
int n,x[maxn],y[maxn],rankx[maxn],ranky[maxn],v[maxn];
struct tree{
    LL sum[maxn],num[maxn];
    void insert(int i,LL v)
    {
        for (;ireturn ;
    }
    LL getsum(int i,LL s=0)
    {
        for (;i;i^=i&-i)
            s+=sum[i];  return s;
    }
    LL getnum(int i,LL s=0)
    {
        for (;i;i^=i&-i)
            s+=num[i];return s;
    }
    LL calcsum(int left,int right)
    {
        return getsum(right)-getsum(left-1); 
    }
    LL calcnum(int left,int right)
    {
        return getnum(right)-getnum(left-1);
    }
}xt,yt;

inline bool box(int a,int b){   return x[a]<x[b];}
inline bool boy(int a,int b){   return y[a]<y[b];}

int main()
{
    scanf("%d%lld",&n,&d);
    for (int i=1;i<=n;i++)
        scanf("%d%d",&x[i],&y[i]),v[i]=i;
    sort(v+1,v+n+1,box);
    for (int i=1;i<=n;i++)  rankx[v[i]]=i,v[i]=i;
    sort(v+1,v+n+1,boy);
    for (int i=1;i<=n;i++)  ranky[v[i]]=i;
    for (int i=1;i<=n;i++)
    {
        ans+=sum+(LL)(i-1)*(x[i]+y[i]);
        sum+=x[i]+y[i];
        ans-=(LL)2*x[i]*xt.calcnum(rankx[i]+1,n);
        ans-=(LL)2*y[i]*yt.calcnum(ranky[i]+1,n);
        ans-=(LL)2*xt.calcsum(1,rankx[i]-1);
        ans-=(LL)2*yt.calcsum(1,ranky[i]-1);
        xt.insert(rankx[i],(LL)x[i]);
        yt.insert(ranky[i],(LL)y[i]);
        if (dreturn printf("%d",i),0;
    }
    printf("-1");
    return 0;
}

你可能感兴趣的:(金色丝线将瞬间一分为二 树状数组的使用)