CF286D - Tourists

首先我们考虑先把区间处理掉,也就是对于每个 \(y\) 轴区间,只保留在这个区间上最早出现的进行贡献,使得每个区间只有一段城墙会贡献到。这个可以离散化然后线段树来处理。

其次,我们发现,对于在时间 \(t_i\) 上出现的 \([l_i,r_i]\) 区间(这里我们说的都是指离散后 \([a,a+1)\) 的区间,也就是 \([l_i,r_i\) 实际上覆盖的实数区间是 \([l_i,r_i+1\)),它会对某一个时间区间内出发的人造成贡献。

具体而言,通过边界分析可得,一个时间 \(t_i\) 出现的 \([l_i,r_i]\) 区间,会对 \(t_i-r_i\) 出发的人造成 \(1\) 的贡献(恰好在最后一格墙出现)。对 \(t_i-l_i\) 出发的人造成 \(r_i-l_i+1\) 的贡献(恰好遇到所有墙)。那么,就是对 \([t_i-r_i-1,t_i-l_i]\) 加上一个公差为 \(0\),首项为 \(0\) 的等差数列。而对 \([t_i-l_i+1,+\infty)\) 加上一个常数 \(r_i-l_i+1\)

我们就把问题转化成了区间加等差数列和区间单点查询。不过时间区间要离散化。而最终查询到的线段树叶子节点是一个时间区间和一个一次函数,直接一次函数求值即可。

int n,m,q,l[200005],r[200005],t[200005],to[200005];
int x[200005],b[400005];
namespace Segtree1{
	int T[1600005];
	inline void add(int i,int L,int R,int x,int l,int r){
		T[i]=min(T[i>>1],T[i]);
		if(l>R||L>r)return;
		if(L<=l&&r<=R){
			T[i]=min(T[i],x);
			return;
		}add(i<<1,L,R,x,l,(l+r)>>1);
		add(i<<1|1,L,R,x,((l+r)>>1)+1,r);
	}
	inline void solve(int i,int L,int R){
		T[i]=min(T[i>>1],T[i]);
		if(L==R){
			if(T[i]!=1e18){
				l[++n]=b[L],r[n]=b[R+1]-1,t[n]=T[i];
			}return;
		}int mid=(L+R)>>1;
		solve(i<<1,L,mid);
		solve(i<<1|1,mid+1,R);
	}
}
inline void restruct(){
	rp(i,n)b[++m]=l[i],b[++m]=r[i];
	sort(b+1,b+1+m);
	m=unique(b+1,b+1+m)-b-1;
	rep(i,0,(m<<2))Segtree1::T[i]=1e18;
	rp(i,n){
		l[i]=lower_bound(b+1,b+1+m,l[i])-b;
		r[i]=lower_bound(b+1,b+1+m,r[i])-b;
		Segtree1::add(1,l[i],r[i]-1,t[i],1,m);
	}n=0;
	Segtree1::solve(1,1,m);
}
bool flag=0;
struct node{
	int l,r,len;
	ll a,k;
}sg[1200005];
inline void init(int i,int l,int r){
	sg[i].l=l,sg[i].r=r,sg[i].a=0,sg[i].k=0;
	sg[i].len=b[r+1]-b[l];
	if(l==r)return;
	init(i<<1,l,(l+r)>>1);
	init(i<<1|1,((l+r)>>1)+1,r);
}
inline void pushdown(int i){
	if(sg[i].l==sg[i].r)return;
	sg[i<<1].a+=sg[i].a,sg[i<<1|1].a+=(sg[i].a+sg[i<<1].len*sg[i].k);
	sg[i<<1].k+=sg[i].k,sg[i<<1|1].k+=sg[i].k;
	sg[i].a=0,sg[i].k=0;
}
inline void add(int i,int l,int r,ll a,ll k){
	pushdown(i);
	if(sg[i].l>r||sg[i].r<l)return;
	if(sg[i].l>=l&&sg[i].r<=r){
		sg[i].a+=a+k*(b[sg[i].l]-b[l]),sg[i].k+=k;
		return;
	}
	add(i<<1,l,r,a,k);
	add(i<<1|1,l,r,a,k);
}
inline ll qry(int i,int x){
	pushdown(i);
	if(b[sg[i].l]>x||b[sg[i].r+1]<=x)return 0;
	if(sg[i].l==sg[i].r){
		return sg[i].a+sg[i].k*(x-b[sg[i].l]+1);
	}return qry(i<<1,x)+qry(i<<1|1,x);
}
signed main(){
	ios::sync_with_stdio(false);
	cin.tie(0);
	cin>>q>>n;
	rp(i,n)cin>>l[i]>>r[i]>>t[i];
	restruct();m=0;
	rp(i,n){
		int L=t[i]-r[i],R=t[i]-l[i];
		l[i]=L,r[i]=R;
		b[++m]=L,b[++m]=R;
	}b[++m]=0;
	sort(b+1,b+1+m);
	m=unique(b+1,b+1+m)-b-1;
	b[m+1]=1e18;
	init(1,1,m);
	rp(i,n){
		l[i]=lower_bound(b+1,b+1+m,l[i])-b;
		r[i]=lower_bound(b+1,b+1+m,r[i])-b;
		add(1,l[i],r[i]-1,0,1);
		add(1,r[i],m,b[r[i]]-b[l[i]]+1,0);
	}
	rp(i,q){
		cin>>x[i];
		cout<<qry(1,x[i])<<endl;
	}
	return 0;
}
//Crayan_r

但是这个做法是可以优化的。具体而言,因为我们两个线段树的操作都是静态的,所以都可以离线下来做。

首先,我们可以对第一个问题将贡献区间排序,然后用 set 记录当前的最小值,直接把每个子区间处理出来,省掉第一个线段树。

然后,我们把加等差数列改成差分数列区间加,前缀和。然后用扫描线的思想优化,记录当前每往后一格的贡献 \(add\) 以及前一位的答案 \(sum\)。每次就是把相邻的 \(q_i\) 之间区间的贡献记录,加上这个区间内新的加入的等差数列的公差以及贡献。而区间加等差数列后缀加末项改成后缀加两个等差数列,一个公差为 \(1\),另一个是 \(-1\),然后我们就可以做了。码量小很多,但是跑的并没有更快。

int n,m,q,l[200005],r[200005],t[200005],b[400005],x[200005];
vt<int>ope[400005];
vt<pii>v;
inline void restruct(){
	rp(i,n)b[++m]=l[i],b[++m]=r[i];
	sort(b+1,b+1+m);
	m=unique(b+1,b+1+m)-1-b;
	rp(i,n){
		l[i]=lower_bound(b+1,b+1+m,l[i])-b;
		r[i]=lower_bound(b+1,b+1+m,r[i])-b-1;
		ope[l[i]].pb(i);
		ope[r[i]+1].pb(-i);
	}set<pii>cur;n=0;
	rep(i,1,m){
		for(auto j:ope[i]){
			if(j>0)cur.insert({t[j],j});
			else cur.erase({t[-j],-j});
		}
		if(cur.size()){
			v.pb({cur.begin()->first-b[i+1]+1,1});
			v.pb({cur.begin()->first-b[i]+1,-1});
		}
	}
}
signed main(){
	ios::sync_with_stdio(false);
	cin.tie(0);
	cin>>q>>n;
	rp(i,n)cin>>l[i]>>r[i]>>t[i];
	restruct();
	sort(v.begin(),v.end());
	reverse(v.begin(),v.end());
	x[0]=-1e9;
	ll sum=0,add=0;
	rp(i,q){
		cin>>x[i];
		sum+=(x[i]-x[i-1])*add;
		while(v.size()&&v.back().first<=x[i]){
			int y=v.back().first,s=v.back().second;
			v.pop_back();add+=s,sum+=s*(x[i]-y+1);
		}cout<<sum<<endl;
	}
	return 0;
}
//Crayan_r
posted @ 2023-05-16 14:42  jucason_xu  阅读(7)  评论(0编辑  收藏  举报