ABC273 模拟 可持久化 区间DP

D

模拟 stl

每次朝一个方向移动x步,有n个障碍,如果有障碍,会被长挡住。问执行m个指令后的结束位置?

有障碍会被挡住,需要我们快速查找移动路径上是否有障碍,这可以set二分,找到大于当前位置的最小障碍/小于当前位置的最大障碍,然后看障碍位置是否在本次移动的路径上。

这样需要对每一行维护所有障碍的y坐标,每一列维护所有障碍的x坐标。考虑到坐标范围很大,行列号也需要离散化,故考虑map<int,set<int>>

E

可持久化思想

一个初始为空的序列,可以插入,删除一个元素,保存,读取副本。

副本肯定不能真的把当前序列存下来,这样就O(q2)O(q^2)O(q2)了。

注意到如果只插入就是一条链,如果删除几个,然后再插入,相当于回退到链上某一点,然后从这一点产生一个新的分支,这样的结构实际上是一棵树。维护这样一颗树即可

如果插入,就再当前节点下面插入一个叶子,如果删除,就回到父亲,如果保存副本,就记录第i个副本,对应的是当前节点编号,读取第i个副本,就是修改当前节点编号为,第i个副本保存的节点。

这样的好处是,我们每次读取副本,实际上是转移到一条链的结尾,而前面的元素,可以多个链共享。

这其实是类似可持久化线段树的思想,可持久化线段树也是多个副本共享无修改的部分,每个副本只在自己修改的部分增加一些节点。

void solve(void){
	int q;
	cin>>q;
	
	int cur=-1;
	vi fa(q+1);
	vi node;
	
	map<int,int>mp;
	rep(i,1,q){
		string op;
		cin>>op;
		int x;
		if(op=="ADD"){
			cin>>x;
			node.push_back(x);
			fa[node.size()-1]=cur;
			cur=node.size()-1;
		}
		else if(op=="DELETE"){
			if(cur!=-1)cur=fa[cur];
		}
		else if(op=="SAVE"){
			cin>>x;
			mp[x]=cur;
		}
		else{
			cin>>x;
			if(!mp.count(x))mp[x]=-1;
			cur=mp[x];
		}
		if(cur==-1)cout<<-1<<' ';
		else cout<<node[cur]<<' ';
	}
}

F

区间DP

数轴上,n个位置有墙,n个位置有锤子,一个锤子可以打破对应一个墙。问从原点到达x的最短用时?

在一个数轴上,或在一个序列上的问题,可以考虑区间dp。

dp(i,j)dp(i,j)dp(i,j)表示使得[i,j][i,j][i,j]内全部可达的最小代价。这样最后答案就是所有包含终点的区间的dp值最小值。

然后只有这些信息,无法转移,因为不知道当前在哪,状态里必须保存一下位置,但是选择区间内哪个位置呢?考虑到使得一个区间全部可达,最优走法最后肯定停在区间端点。所以状态记录最后停在左端点还是右端点。这样转移就是从一个区间端点,移动到新的区间的端点。

转移时,区间往外拓展一个位置,当新位置没有墙,或者油枪,但是墙对应地锤子在当前区间内部,则可以转移。

墙的坐标可能是负数,且范围很大,考虑离散化。由于计算距离要用原始数据,考虑使用排序二分的方式离散化,可以保存原始数据。

int f[3030][3020][2];
void solve(void){
	int n,x;
	cin>>n>>x;
	memset(f,0x3f,sizeof f);
	vi a(n+1),b(n+1),all;
	all.push_back(-1e18);
	rep(i,1,n){
		cin>>a[i];		
		all.push_back(a[i]);
	}
	rep(i,1,n){
		cin>>b[i];
		all.push_back(b[i]);
	}
	int s=0;
	all.push_back(s);
	all.push_back(x);
	sort(all.begin(),all.end());
	all.erase(unique(all.begin(),all.end()),all.end());
	
	int m=all.size()-1;
	vi id(m*2+10);
	rep(i,1,n){
		a[i]=lower_bound(all.begin(),all.end(),a[i])-all.begin();
		b[i]=lower_bound(all.begin(),all.end(),b[i])-all.begin();
		id[a[i]]=i;
	}
	s=lower_bound(all.begin(),all.end(),s)-all.begin();
	x=lower_bound(all.begin(),all.end(),x)-all.begin();
	
	f[s][s][0]=f[s][s][1]=0;
	
	rep(i,1,m-1){
		rep(l,1,m){
			int r=l+i-1;
			if(l>1){
				int p=id[l-1];
				if(!p||b[p]>=l&&b[p]<=r){
					f[l-1][r][0]=min(f[l][r][0]+all[l]-all[l-1],f[l-1][r][0]);
					f[l-1][r][0]=min(f[l][r][1]+all[r]-all[l-1],f[l-1][r][0]);
				}
			}
			if(r<m){
				int p=id[r+1];
				if(!p||b[p]>=l&&b[p]<=r){
					f[l][r+1][1]=min(f[l][r][1]+all[r+1]-all[r],f[l][r+1][1]);
					f[l][r+1][1]=min(f[l][r][0]+all[r+1]-all[l],f[l][r+1][1]);
				}
			}
		}
	}
	
	int ans=1e18;
	rep(i,1,x){
		rep(j,x,m){
			ans=min({ans,f[i][j][0],f[i][j][1]});
		}
	}	

	if(ans<1e18)cout<<ans;
	else cout<<-1;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值