bzoj4979凌晨三点的宿舍 分治+树状数组+扫描线

21 篇文章 0 订阅
3 篇文章 0 订阅

Description

Q所在的学校QNU(Quailty Niubi University)的学生公寓由n栋楼组成,这些楼从左往右连成一排,编号依次为1
到n,其中第i栋楼有h_i层。现在已经凌晨三点了,但是小Q和他的队友们仍然在刻苦地刷题,从他们房间窗户透出
的亮光格外醒目。

这时,辛苦了一晚上的小Q饿了,正当他拆完泡面准备倒水的时候,他发现热水壶放在队友那忘记拿回来了。无奈
之下,他只好走路去向队友要回热水壶。在学生公寓里,小Q每分钟可以选择上楼或者下楼,或者往左往右移动到
相邻的房间外面的过道上。因为夜晚的门禁,他不能走出公寓,同时,他也不能登上天台思考人生。你可以认为从
房间到过道不需要任何时间。饥饿的小Q最多只能坚持走k分钟,再之后他就会因为饥饿而昏倒。他历尽艰险终于从
队友那拿回了热水壶,解决了燃眉之急。这时,吃着泡面的小Q开始思考一个问题:如果他和队友的房间距离并没
有那么近,那么他就不会这么走运了。于是现在他想知道有多少对队友的房间之间的最短路程不超过k分钟。小Q正
在吃泡面,于是他把这个问题交给了你。请写一个程序帮助小Q解决这个现实问题。

题解

好题啊好题(向Claris低头QAQ)
此题做的时候只是扫了一眼,以为是双指针O(n)xjb维护一下,后来考完认真想了想发现萎的,无论怎么样都不能维护区间答案。。
%Claris
分治的想法比较显然了。
先把每一列上的处理一下,如果是同一列的双指针扫一遍就可以了。
分治处理不在同一列的点对。递归处理只在左半边和右半边,问题是如何处理跨越左右的。
dis(A,B)=xBxA+yA+yB2min(yA,yB,hxA,hxA+1,...,hxB)
dis(A,B)=xBxA+yA+yB2min(min(yA,fxA),min(yB,gxB))
那么,我们先预处理出f,g,然后用扫描线,双指针把左右区间按照min值从大到小处理一下,每次对于一个区间,用树状数组记录答案,然后直接加入,所以注意加一个时间戳,不同区间不能混了。

#include<cstdio>
#include<algorithm>
#include<cstring>
#define fo(i,a,b) for(int i=a;i<=b;i++)
#define fd(i,a,b) for(int i=a;i>=b;i--)
using namespace std;
const int N=2e5+10;
const int M=N*3;
int n,m,lim;
typedef long long ll;
ll ans;
int a[N],next[N],t[M],vis[M];
int tot,head[N],go[N],T;
struct node
{
    int w,x,y;
    node(){}
    node(int w1,int x1,int y1)
    {
        w=w1;
        x=x1;
        y=y1;
    }
}qa[N],qb[N];
inline int lowbit(int x)
{
    return x&(-x);
}
inline bool cmp(const node&a,const node&b)
{
    return a.w<b.w; 
}
inline void add(int x,int y)
{
    go[++tot]=y;
    next[tot]=head[x];
    head[x]=tot;
}
inline void work(int x)
{
    int num=0;
    static int b[N];
    for(int i=head[x];i;i=next[i])
    b[++num]=go[i];
    if (num<=1)return;
    sort(b+1,b+1+num);
    int j=1;
    fo(i,1,num)
    {
        while (j<num&&b[j+1]-b[i]<=lim)j++;
        ans+=j-i;
    }
}
inline void ins(int x)
{
    x+=N;
    while(x<M)
    {
        if (vis[x]<T)vis[x]=T,t[x]=1;
        else t[x]++;
        x+=lowbit(x);
    }
}
inline void ask(int x)
{
    x=min(x+N,M-1);
    while (x>0)
    {
        if (vis[x]==T)ans+=t[x];
        x-=lowbit(x);
    }
}
inline void solve(int l,int r)
{
    if (l==r)return;
    int mid=(l+r)>>1,toa=0,tob=0;
    solve(l,mid),solve(mid+1,r);
    int k=N;
    fd(i,mid,l)
    {
        if (k>a[i])k=a[i];
        for(int j=head[i];j;j=next[j])
        qa[++toa]=node(min(k,go[j]),i,go[j]);
    }
    k=N;
    fo(i,mid+1,r)
    {
        if (k>a[i])k=a[i];
        for(int j=head[i];j;j=next[j])
        qb[++tob]=node(min(k,go[j]),i,go[j]);
    }
    if (!toa||!tob)return;
    sort(qa+1,qa+1+toa,cmp);
    sort(qb+1,qb+1+tob,cmp);
    int i,j;
    for(T++,i=toa,j=tob;i;i--)
    {
        while (j&&qa[i].w<=qb[j].w)ins(qb[j].y+qb[j].x),j--;
        ask(lim+2*qa[i].w+qa[i].x-qa[i].y);
    }
    for(T++,i=tob,j=toa;i;i--)
    {
        while (j&&qb[i].w<qa[j].w)ins(qa[j].y-qa[j].x),j--;
        ask(lim+2*qb[i].w-qb[i].x-qb[i].y);
    }
}
int main()
{
    scanf("%d%d",&n,&lim);
    fo(i,1,n)scanf("%d",&a[i]);
    scanf("%d",&m);
    fo(i,1,m)
    {
        int x,y;
        scanf("%d%d",&x,&y);
        add(x,y);
    }
    fo(i,1,n)work(i);
    solve(1,n);
    printf("%lld\n",ans);
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值