[模拟赛]Boss

Boss

题目大意

给定 \(n\) 组区间,要求你从第 \(1\) 组区间向第 \(n\) 组区间转移。

第 \(i\) 组区间有 \(k_i\) 个区间。

你可以选择任意开始位置,即你可以选择任意一个被第一组区间覆盖的位置开始,每次转移要求必须到下一组区间覆盖的位置。

向左移动一个单位长度的代价为 \(A\) ,向右移动一个单位长度的代价为 \(B\) ,求最小转移代价。

\(1\leq n\leq 2\times 10^5,1\leq A,B\leq 10^6,\sum k_i \leq 10^6,1\leq l_i\leq r_i\leq q\)

其中 \(q\) 是合法位置的取值范围,且 \(1\leq q\leq 1e6\) 。

分析

“走多必失”

首先,我们能够发现一个比较显然的性质,我们只需要维护各个端点的取值。

考虑从状态 \(x\) 转移到安全范围,无论向左还是向右,显然我们都应该到达最近的安全区间的端点。

以向左为例,如果到达的不是端点,那么若下一次更新要到达端点右边的位置,显然是更劣的,而若端点要到达左边的某个位置,显然是不亏的,我们发现,其实对于一个状态,需要的是少走尽量不走,这样是不劣的。

但是我们很快就能发现,状态 \(x\) 甚至能够不走,但是如果它不走,它更新到的位置就不是端点,由于不走肯定比走优,我们就不用它更新两个端点,新开一个状态点,让它不走,继承当前状态的答案。

选择起点

由于起点任选,这个会给我们带来什么影响?

由于我们一开始的状态全是端点,但是有可能在某个区间的内部不动更新到某个状态点。

所以我们能够维护一棵线段树,每一组区间更新完成后做一次区间加,之后每一组区间更新前算一下当前状态点处在线段树上的值是不是当前组数减 \(1\) ,如果是,显然这个位置一直被区间覆盖,将这个状态更新为 \(0\) 。

这样我们似乎就解决了起点的问题。

复杂度

区间的数量最多是 \(10^6\) ,那么状态点的总和最多就是 \(2\times 10^6\) ,我们每一轮新开状态点也可能舍弃状态点,可以预见状态点的和多半不会增长太多,减少也说不定。

对于每一个状态点和区间,我们做一遍线段树,线段树大小为有效区间大小,为 \([1,10^6]\) ,即 \(q\) 。

最后的复杂度为 \(O(klogq)\) 。

CODE

#include <bits/stdc++.h>
using namespace std;
const long long INF=2e18;
const int N=2e5+10,M=1e6+10,lim=1e6;
inline int read()
{
	int s=0,w=1;
	char ch=getchar();
	while(ch<'0'||ch>'9') { if(ch=='-') w*=-1; ch=getchar(); }
	while(ch>='0'&&ch<='9') s=s*10+ch-'0',ch=getchar();
	return s*w;
}
struct node{ int l,r; };
int n;
long long A,B;
int tot[2],point[2][M];
long long f[2][M]; //滚动记录答案 
int tr[4*M];
vector<node> arr[N]; 
inline void change(int k,int l,int r,int x,int y) //线段树记录选位问题 
{
	if(l>y||r<x) return;
	if(l>=x&&r<=y) {
		tr[k]++;
		return;
	}
	int mid=(l+r)/2;
	change(k*2,l,mid,x,y),change(k*2+1,mid+1,r,x,y);
}
inline int ask(int k,int l,int r,int x,int v) //询问点值 
{
	if(l==r&&l==x) return tr[k]+v;
	int mid=(l+r)/2;
	if(x<=mid) return ask(k*2,l,mid,x,v+tr[k]);
	else return ask(k*2+1,mid+1,r,x,v+tr[k]);
}
inline int findl(int l,int r,int x,int id)
{
	int res=-1;
	while(l<=r){
		int mid=(l+r)/2;
		if(arr[id][mid].l<=x) l=mid+1,res=mid;
		else r=mid-1;
	}
	return res;
}
inline int findr(int l,int r,int x,int id)
{
	int res=-1;
	while(l<=r){
		int mid=(l+r)/2;
		if(arr[id][mid].r>=x) r=mid-1,res=mid;
		else l=mid+1;
	}
	return res;
}
inline void update(int t,int x,int opt)
{
	int l=findl(0,arr[t+1].size()-1,x,t+1); //注意,这个点不是某一个端点 
	int r=findr(0,arr[t+1].size()-1,x,t+1);
	if(l!=-1&&arr[t+1][l].r>=x){ //存在一个这样的区间包含了当前端点 
		if(arr[t+1][l].l<x&&arr[t+1][l].r>x) //开一个新点
			point[opt^1][++tot[opt^1]]=x; 
		f[opt^1][x]=min(f[opt^1][x],f[opt][x]); //这个点不用动
	//	printf("%d %lld\n",t,f[opt^1][x]);
	}
	else{ //向左或向右 
		if(l!=-1){
			f[opt^1][arr[t+1][l].r]=min(f[opt^1][arr[t+1][l].r],f[opt][x]+(long long)(x-arr[t+1][l].r)*A);
		//	printf("%d %lld\n",t,f[opt^1][arr[t+1][l].r]);
			//if(f[opt^1][arr[t+1][l].r]<0) puts("-1"),printf("%d %lld %lld %lld\n",t,f[opt^1][arr[t+1][l].r],f[opt][x],x-arr[t+1][l].r);
		//	if(x-arr[t+1][l].r<0) puts("-2");
		}
		if(r!=-1){
			f[opt^1][arr[t+1][r].l]=min(f[opt^1][arr[t+1][r].l],f[opt][x]+(long long)(arr[t+1][r].l-x)*B);
		//	printf("%d %lld\n",t,f[opt^1][arr[t+1][r].l]);
			//if(f[opt^1][arr[t+1][r].l]<0) puts("-1"),printf("%d $lld %lld %lld\n",t,f[opt^1][arr[t+1][r].l],f[opt][x],arr[t+1][r].l-x);
		//	if(arr[t+1][r].l-x<0) puts("-2");
		}
	}
}
int main()
{
//	freopen("boss.in","r",stdin);
//	freopen("data.out","w",stdout);
	n=read(),A=read(),B=read();
	for(register int i=1;i<=n;i++){
		int x=read();
		for(register int j=1;j<=x;j++){
			int x=read(),y=read();
			arr[i].push_back((node){x,y});
		}
	}
	memset(f[1],0x3f,sizeof(f[1]));
//	cout<<f[0][0]<<" "<<f[1][0]<<endl;
	int opt=0;
	for(register int t=1;t<n;t++){
		if(t>1){
			for(register int i=0;i<arr[t].size();i++){ //判断是否可以通过选择中间站位达到该点 
				if(ask(1,1,lim,arr[t][i].l,0)==t-1) f[opt][arr[t][i].l]=0;
				if(ask(1,1,lim,arr[t][i].r,0)==t-1) f[opt][arr[t][i].r]=0;
			}
		}
//		if(tot[opt]){
//			for(register int i=1;i<=tot[opt];i++)
//				if(ask(1,1,lim,point[opt][i],0)==t-1) f[opt][point[opt][i]]=0;
//		}
		
		for(register int i=1;i<=tot[opt];i++){
			update(t,point[opt][i],opt);
			f[opt][point[opt][i]]=INF;
		}
		
		tot[opt]=0; //更新完成清空 
		
		for(register int i=0;i<arr[t].size();i++){
			if(f[opt][arr[t][i].l]<INF) update(t,arr[t][i].l,opt); //存在此状态才能更新
			f[opt][arr[t][i].l]=INF;
			if(arr[t][i].l==arr[t][i].r) continue;
			if(f[opt][arr[t][i].r]<INF) update(t,arr[t][i].r,opt);
			f[opt][arr[t][i].r]=INF;
		}
		
		for(register int i=0;i<arr[t].size();i++)
			change(1,1,lim,arr[t][i].l,arr[t][i].r);
		
		if(t==1) memset(f[opt],0x3f,sizeof(f[opt]));
		opt^=1;
	}
	for(register int i=0;i<arr[n].size();i++){ //判断是否可以通过选择中间站位达到该点 
		if(ask(1,1,lim,arr[n][i].l,0)==n-1) f[opt][arr[n][i].l]=0;
		if(ask(1,1,lim,arr[n][i].r,0)==n-1) f[opt][arr[n][i].r]=0;
	}
	long long ans=INF;
	for(register int i=1;i<=lim;i++) 
		ans=min(ans,f[opt][i]);
	printf("%lld\n",ans);
	return 0;
}
上一篇:软件架构设计原则之迪米特法则


下一篇:从《无主之地3》探讨,为什么刷子游戏能让玩家心甘情愿的肝硬化