CF780G Andryusha and Nervous Barriers 题解

发布时间 2023-10-02 20:16:43作者: yanghanyv

来个不一样的做法:扫描线,线段树上二分。

思路

我们发现只需找到小球落到每个挡板后的下一个挡板,就可以建出一张 DAG,在 DAG 上简单 DP 即可求方案。
所以我们考虑怎么建图。
大多人用扫描线是从下到上扫描的,但我们考虑从左到右扫描。
我们在挡板左端做加入操作,右端做删除操作,对于扫描中每一个横坐标,我们可以用线段树维护出每个高度的挡板编号。
对于第 \(i\) 号挡板,我们需要在横坐标为 \(l_i-1\)\(r_i+1\) 时,找到最高的挡板 \(j\),满足 \(u_j < u_i\)\(u_j + s_j \geq u_i\)
显然,我们只要在线段树上维护区间 \(u_j + s_j\) 的最大值,就可以线段树上二分,对每个 \(i\) 求出对应的 \(j\)
时间复杂度 \(O((n + w)\log{n})\)

代码

#include <bits/stdc++.h>
using namespace std;
const int N=1e5+5;
const int MOD=1e9+7;
int Add(int a,int b){
	return (a+b)%MOD;
}
int Mul(int a,int b){
	return 1ll*a*b%MOD;
}
int h,w,n,u[N],s[N],ord[N],rk[N],cnt,f[N];
vector<int> lh,ne[N];
struct operation{
	int t,id,x;
	bool operator < (const operation &B) const{
		//注意排序时的优先级,这个需要全面的考虑 
		if(x!=B.x){
			return x<B.x;
		}else{
			if(t!=B.t){
				return t<B.t;
			}else{
				if(t){
					return u[id]>u[B.id];
				}else{
					return u[id]<u[B.id];
				}
			}
		}
	}
}op[2*N];
struct SGT{//线段树 
	struct node{
		int l,r,val;
	}tree[4*N];
	void pushup(int p){
		tree[p].val=max(tree[2*p].val,tree[2*p+1].val);
	}
	void build(int p,int l,int r){
		tree[p]={l,r,0};
		if(l<r){
			int mid=(l+r)/2;
			build(2*p,l,mid);
			build(2*p+1,mid+1,r);
		}
	}
	void modify(int p,int x,int v){
		int l1=tree[p].l,r1=tree[p].r;
		if(l1==r1){
			tree[p].val=u[v]+s[v];
		}else{
			int mid=(l1+r1)/2;
			if(x<=mid){
				modify(2*p,x,v);
			}else{
				modify(2*p+1,x,v);
			}
			pushup(p);
		}
	}
	int query(int p,int x,int v){
		int res=0;
		if(x){
			int l1=tree[p].l,r1=tree[p].r;
			if(l1==r1){
				if(tree[p].val>=v){
					res=r1;
				}
			}else{
				int mid=(l1+r1)/2;
				if(x>mid){
					res=query(2*p+1,x,v);
				}
				if(!res&&tree[2*p].val>=v){
					res=query(2*p,x,v);
				}
				//线段树上二分的精髓在于上面的两个if,如果写错复杂度就会不对 
			}
		}
		return res;
	}
}tr;
int main(){
	scanf("%d%d%d",&h,&w,&n);
	for(int i=1;i<=n;i++){
		int l,r;
		scanf("%d%d%d%d",&u[i],&l,&r,&s[i]);
		ord[i]=i;
		if(l-1){
			op[++cnt]={1,i,l-1};
		}else{
			lh.push_back(i);//所有靠到1的挡板特殊处理 
		}
		if(r+1<=w){
			op[++cnt]={0,i,r+1};
		}
	}
	sort(ord+1,ord+n+1,[&](int x,int y){
		return u[x]<u[y];
	});
	for(int i=1;i<=n;i++){
		rk[ord[i]]=i;
	}
	//ord[i]指第i低的挡板编号,rk[i]指挡板i的高度排名 
	sort(op+1,op+cnt+1);
	tr.build(1,1,n);
	for(int i=0;i<lh.size();i++){
		tr.modify(1,rk[lh[i]],lh[i]);//一定要加入靠到1的挡板 
	}
	for(int i=1,j=1;i<=w;i++){
		while(j<=cnt&&op[j].x==i&&!op[j].t){
			int idx=op[j].id;
			tr.modify(1,rk[idx],0);//删除挡板 
			ne[rk[idx]].push_back(tr.query(1,rk[idx]-1,u[idx]));
			j++;
		}
		f[tr.query(1,n,h+1)]++;
		while(j<=cnt&&op[j].x==i&&op[j].t){
			int idx=op[j].id;
			ne[rk[idx]].push_back(tr.query(1,rk[idx]-1,u[idx]));
			tr.modify(1,rk[idx],idx);//加入挡板 
			j++;
		}
	}
	for(int i=n;i>=1;i--){
		if(ne[i].size()==1){
			f[ne[i][0]]=Add(f[ne[i][0]],Mul(2,f[i]));
		}else{
			f[ne[i][0]]=Add(f[ne[i][0]],f[i]);
			f[ne[i][1]]=Add(f[ne[i][1]],f[i]);
		}
	}
	//DAG上DP 
	printf("%d",f[0]);
	return 0;
}