把博客园图标替换成自己的图标
把博客园图标替换成自己的图标end

【BZOJ1171】大sz的游戏(线段树+单调队列)

点此看题面

大致题意:\(n\)个点,两点间最大通讯距离为\(L\)。已知除\(1\)号点外第\(i\)个点能够发出和接收的信号区间\([l_i,r_i]\)以及到\(1\)号点的距离\(dis_i\)\([l_1,r_1]\)\([0,INF]\))。对于两个点\(i,j\),只有满足\(i<j\)\([l_i,r_i]\)\([l_j,r_j]\)有交集且\(dis_j-dis_i\le L\)时,才会有一条从\(j\)\(i\)的有向边。求每个点到\(1\)号点的最短距离(无法到达输出\(-1\))。

一个暴力的单调队列做法

首先,对于这道题应该比较容易想到用单调队列去搞。

我们考虑对于每一种信号波长开一个单调队列(离散化之后就只有\(2n\)个),用来记录对这种波长有影响的点的编号,维护这些点的答案单调递增(因为后加入的肯定可以覆盖先加入的较为不优的答案)。

初始化,在每个单调队列里扔一个\(1\)

下一步,我们从\(2\)\(n\)枚举\(i\)

对于当前的\(i\),我们首先枚举每个队列,将队首的几个\(dis_i-dis_x>L\)的数给弹掉(因为队列中的元素显然递增,所以\(dis\)值也递增,故只需处理队首)。

然后,我们在\([l_i,r_i]\)范围内的所有单调队列中,求出它们队首的最小值\(k\)

\(ans_i\)即为\(k+1\)

接下来,我们再把\(i\)给扔入\([l_i,r_i]\)范围内的所有单调队列中。

这样就完成了对于一个点的操作。

不难发现这样显然会\(T\)飞,因此需要优化。

线段树优化(标记永久化)

看到区间询问队首最小值、区间扔数,我们应该比较容易想到线段树

然而,只对每个叶子节点各开一个单调队列,显然是不好维护的,每次加入的数的规模依然是\(O(n)\)

那么我们就要用到一个比较实用的线段树优化技巧:标记永久化

在这题中,这个优化的具体实现就是,对于每一个非叶子节点,我们也开一个单调队列,来表示这棵子树内所有叶节点的单调队列都需要加入这个单调队列中的数

然后一次修改就只需要加入\(O(logn)\)个数。

而询问,我们记下来每个节点子树内的队首最小值\(Mn_i\),然后对于第\(i\)个点,它实际的队首最小值就是\(Mn_i\)到根节点路径上所有祖先节点的队首最小值中的较小值。

后者可以在从上往下查询的时候顺带统计出来。

具体实现可以详见代码。

关于\(STL\)\(deque\)\(list\)

由于要给每个点开单调队列,不会写指针的我就只能借助\(STL\)的双端队列\(deque\)了。

然而,关于\(deque\),它\(MLE\)了。。。

可更神奇的是,当我把\(deque\)换成\(list\),而其他地方原封不动,却过了!

关于这一点,我和\(hl666\)通过猜测,得出了一个似乎较为合理的结论:

  • \(deque\)是用迭代器实现的,所以内存大,而它的优势在于可以使用\(iterator\)进行遍历。
  • \(list\)应该是用指针实现的,所以内存小。

也算是涨知识了。

代码

#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 250000
#define INF 1e9
#define Gmin(x,y) (x>(y)&&(x=(y)))
#define Gmax(x,y) (x<(y)&&(x=(y)))
#define min(x,y) ((x)<(y)?(x):(y))
#define GV(x) (lower_bound(dv+1,dv+dc+1,x)-dv)
using namespace std;
int n,m,dc,l[N+5],r[N+5],dis[N+5],ans[N+5],dv[(N<<1)+5];
class FastIO
{
    private:
        #define FS 100000
        #define tc() (A==B&&(B=(A=FI)+fread(FI,1,FS,stdin),A==B)?EOF:*A++)
        #define pc(c) (C^FS?FO[C++]=c:(fwrite(FO,1,C,stdout),FO[(C=0)++]=c))
        #define tn (x<<3)+(x<<1)
        #define D isdigit(c=tc())
        int T,C;char c,*A,*B,FI[FS],FO[FS],S[FS];
    public:
        I FastIO() {A=B=FI;}
        Tp I void read(Ty& x) {x=0;W(!D);W(x=tn+(c&15),D);}
        Tp I void write(Ty x) {x<0&&(pc('-'),x=-x);W(S[++T]=x%10+48,x/=10);W(T) pc(S[T--]);}
        Ts I void read(Ty& x,Ar&... y) {read(x),read(y...);}
        Tp I void writeln(Con Ty& x) {write(x),pc('\n');}
        I void clear() {fwrite(FO,1,C,stdout),C=0;}
}F;
class MonQueue//单调队列
{
    private:
        list<int> q;
    public:
        I MonQueue() {q.clear();}I int Front() {return q.empty()?INF:ans[q.front()];}//求队首,为空时返回INF
        I void Pop(CI x) {!q.empty()&&!(q.front()^x)&&(q.pop_front(),0);}//若有,则弹出值为x的元素
        I void Push(CI x) {W(!q.empty()&&ans[q.back()]>=ans[x]) q.pop_back();q.push_back(x);}//加入一个新元素,注意维护答案单调递增
};
class SegmentTree//线段树
{
    private:
        #define SZ 1048573//卡内存,实践可得用到的叶节点中最大编号为1048573
        #define STO l,hl,rt<<1,tl,tr
        #define ORZ hl+1,r,rt<<1|1,tl,tr
        #define PU(x) (O[x].Mn=O[x].Q.Front(),(x<<1)<=Mx&&(Gmin(O[x].Mn,O[x<<1].Mn),Gmin(O[x].Mn,O[x<<1|1].Mn)))//特判左、右儿子编号超过最大编号时不上传信息,也用于卡内存
        int Mx;struct node {int Mn;MonQueue Q;I node() {Mn=INF;}}O[SZ+5];
        I void ins(CI l,CI r,CI rt,CI tl,CI tr,CI v)//插入
        {
            if(tl<=l&&r<=tr) return (void)(O[rt].Q.Push(v),PU(rt));RI hl=l+r>>1;
            tl<=hl&&(ins(STO,v),0),tr>hl&&(ins(ORZ,v),0),PU(rt);
        }
        I void del(CI l,CI r,CI rt,CI tl,CI tr,CI v)//删除
        {
            if(tl<=l&&r<=tr) return (void)(O[rt].Q.Pop(v),PU(rt));RI hl=l+r>>1;
            tl<=hl&&(del(STO,v),0),tr>hl&&(del(ORZ,v),0),PU(rt);
        }
        I int qry(CI l,CI r,CI rt,CI tl,CI tr)//询问
        {
            if(tl<=l&&r<=tr) return O[rt].Mn;RI hl=l+r>>1,res=O[rt].Q.Front(),t;
            return tl<=hl&&(t=qry(STO),Gmin(res,t)),tr>hl&&(t=qry(ORZ),Gmin(res,t)),res;
        }
    public:
        I void Init(CI l=1,CI r=dc,CI rt=1)//初始化
        {
            if(Gmax(Mx,rt),!(l^r)) return;RI hl=l+r>>1;
            Init(l,hl,rt<<1),Init(hl+1,r,rt<<1|1);
        }
        I void Insert(CI x) {ins(1,dc,1,l[x],r[x],x);}I void Delete(CI x) {del(1,dc,1,l[x],r[x],x);}
        I int Query(CI x) {return qry(1,dc,1,l[x],r[x]);}
}S;
int main()
{
    RI i,p=1;for(F.read(n,m),i=2;i<=n;++i) F.read(l[i],r[i],dis[i]),dv[(i-1<<1)-1]=l[i],dv[i-1<<1]=r[i];
    sort(dv+1,dv+(n-1<<1)+1),dc=unique(dv+1,dv+(n-1<<1)+1)-dv-1;//离散化
    for(l[1]=1,r[1]=dc,S.Init(),S.Insert(1),i=2;i<=n;++i)//初始化+枚举i
    {
        l[i]=GV(l[i]),r[i]=GV(r[i]);W(dis[i]-dis[p]>m) S.Delete(p++);//弹出队首不符合条件的数(由于被弹掉的数必定从1开始每次递增1,因此可以直接开个变量存储)
        F.writeln((ans[i]=S.Query(i)+1)>=INF?-1:ans[i]),S.Insert(i);//求解并输出答案,然后扔入单调队列
    }return F.clear(),0;
}
posted @ 2019-04-08 19:48  TheLostWeak  阅读(310)  评论(0编辑  收藏  举报