线段树&树状数组与离散化的妙用

牛客2019多校联盟Day7 Fine the median

题意:  每次给数组插入区间[Li,Ri] 内的所有数,每操作一次查询中位数。

遇到这题真的算是巧合,然而就是这种冥冥之中的缘分,给了我线段树的一个全新的思想。

这题与以往的线段树做法有所不同,常规的线段树题目,可能就是一个点区间代表一个数据,然后找个区间最大值什么的。

这种很显而易见的,对吧?因为点区间代表了一个点,仅仅是一个点,所以做起来比较容易。

而今天这题十分有趣。它的点区间代表的是一个区间。

比如这题,我们去怎么解决呢?

对于更新,所有要加入的区间,我们先离散化。比如我们要加【1,3】【3,6】 .去重之后的数组就是【1,3,6】,之后处理【1,3】这个操作的时候,直接lower_bound定位他在离散数组里面的那个位置,以这个位置做为线段树的建树和后续查询的依据。

对于查询,我们要定义一个数组代表线段树里面的某个区间加了多少个数。如果左子区间的个数大于中位数所在的位置,就去左子区间;如果右子区间的个数大于中位数所在的位置,那么我们就把中位数所在的位置减去左子区间的个数。

其实这题妙在线段树与离散化数组构成的联系,线段树的区间就是刚好对应了离散数组的位置。详情看代码

#include <bits/stdc++.h>
#define maxn 400005
using namespace std;
typedef long long ll;
int i,n,zuo,you;
ll sum,x[maxn],y[maxn],A1,A2,B1,B2,C1,C2,M1,M2,a[maxn*6],lazy[maxn*6],G[maxn*2],L[maxn],R[maxn];
void push_up(int num)
{
    a[num]=a[num*2]+a[num*2+1];
}
void push_down(int l,int r,int num)
{
    if (lazy[num]==0) return;
    int mid=(l+r)/2;
    lazy[num*2]+=lazy[num];
    lazy[num*2+1]+=lazy[num];
    a[num*2]+=(G[mid+1]-G[l])*lazy[num];
    a[num*2+1]+=(G[r+1]-G[mid+1])*lazy[num];
    lazy[num]=0;
}
void upgrade(int l,int r,int num)
{
    if (zuo<=l && r<=you)
    {
        lazy[num]+=1;
        a[num]+=G[r+1]-G[l];
        return;
    }
    push_down(l,r,num);
    int mid=(l+r)/2;
    if (mid>=zuo) upgrade(l,mid,num*2);
    if (mid<you) upgrade(mid+1,r,num*2+1);
    push_up(num);
}
ll query(int l,int r,int num,ll sum)
{
    if (l==r)
    {
        int ti=a[num]/(G[r+1]-G[l]);
        return G[l]+(sum-1)/ti;
    }
    push_down(l,r,num);
    int mid=(l+r)/2;
    if (a[num*2]>=sum) return query(l,mid,num*2,sum);
        else return query(mid+1,r,num*2+1,sum-a[num*2]);
}
int main()
{
    cin>>n;
    scanf("%lld%lld%lld%lld%lld%lld",&x[1],&x[2],&A1,&B1,&C1,&M1);
    scanf("%lld%lld%lld%lld%lld%lld",&y[1],&y[2],&A2,&B2,&C2,&M2);
    for (i=3;i<=n;i++) 
    {
        x[i]=(A1*x[i-1]+B1*x[i-2]+C1)%M1;
        y[i]=(A2*y[i-1]+B2*y[i-2]+C2)%M2;
    }
    for (i=1;i<=n;i++)
    {
        L[i]=min(x[i],y[i])+1;
        R[i]=max(x[i],y[i])+1+1;
        G[i]=L[i];
        G[i+n]=R[i];
    }
    sort(G+1,G+1+2*n);
    int element=unique(G+1,G+1+2*n)-G-1;
    for (i=1;i<=n;i++)
    {
        sum+=R[i]-L[i];
        zuo=lower_bound(G+1,G+1+element,L[i])-G;
        you=lower_bound(G+1,G+1+element,R[i])-G-1;
        upgrade(0,element,1);
        printf("%lld
",query(0,element,1,(sum+1)/2));
    }
    return 0;
 } 
View Code

CF round 624 F

看到这题的时候就感觉有些熟悉,但是树状数组写的很少,于是一开始就想着用线段树去解决,然后看了看数据范围,感觉线段树很有可能会爆,于是匆匆收场。

其实当时我还想着一个比较傻b的问题,A点在B点后面,速度比B要大,相隔距离最短的时候是多少米。对,没错,这道高中物理送分题,我竟然想了一段时间。枯了

我们可以看到有两种情况

一个点在另一个点的后面,速度比它小,那么相隔距离最短的时候就是0秒的时候

一个点在另一个点的后面,速度比它大,那么它们两个一定能相遇!

所以我们把点的坐标、速度分别从小到大排序。我们这里不用查重,因为查重之后就找不到对应的位置了。

以点的大小为依据,又分两种情况

一个是速度为负,一个是速度为正,对于两点之间速度相反的点,我们直接相加和他们的初始的距离。这里可能会有疑问?如果当前处理的点速度为正,下一个要循环的点速度为负且这个点在当前点的右边,那么它们不会相遇吗?这里我们进行一个处理,对于速度为负的点,我们只处理与速度为负的其他点的关系;对于速度为正的点,我们就全部处理。

那么速度相向的问题解决了,速度方向相同呢?

如果0s的时候是最佳答案,我们把当前点的速度就存在树状数组里,我们在下一个点的处理,把两点之间距离加上。

我们要清楚这里是标量之间比较大小,所以数字小的速度肯定会被数字大的速度累加到,累加这个过程就有点前缀和的味道,具体看程序。

#include <bits/stdc++.h>
typedef long long ll;
using namespace std;
struct node
{
    int x,v;
};
const int N = 2e5 + 10;
node a[N];
int n,i,v[N];
ll z[N], f[N], zhsum[N], fusum[N],num,ans; 
bool cmp(node x,node y)
{
    return x.x<y.x;
}
int lowbit(int x) 
{
    return x & (-x);
}
ll sum(int x,ll d[])
{
    ll cnt=0;
    while (x)
    {
        cnt+=d[x];
        x-=lowbit(x);
    }
    return cnt;
}
void add(int x,int k,ll d[])
{
    while (x<N)
    {
        d[x]+=k;
        x+=lowbit(x);
    }
}
int main()
{
    scanf("%d",&n);
    for (i=1;i<=n;i++) scanf("%d",&a[i].x);
    for (i=1;i<=n;i++) 
    {
        scanf("%d",&a[i].v);
        v[i]=a[i].v;
    }
    sort(v+1,v+1+n);
    sort(a+1,a+1+n,cmp);
    for (i=1;i<=n;i++)
    {
        if (a[i].v<0)
        {
            num=lower_bound(v+1,v+1+n,a[i].v)-v;
            ans+=sum(num,fusum)*a[i].x-sum(num,f);
            add(num,a[i].x,f);
            add(num,1,fusum);
        }
        else
        {
            num=lower_bound(v+1,v+1+n,a[i].v)-v;
            ans+=sum(N-1,fusum)*a[i].x-sum(N-1,f);
            ans+=sum(num,zhsum)*a[i].x-sum(num,z);
            add(num,a[i].x,z);
            add(num,1,zhsum);
        }
    }
    cout<<ans<<endl;
    return 0;
 } 
View Code
原文地址:https://www.cnblogs.com/Y-Knightqin/p/12362677.html