Codeforces1201D Treasure Hunting 递推(毒瘤!)

本文解析了一道关于在地图上收集宝藏的算法题,通过优化策略和使用单调队列,将复杂度降低至O(nlogq),并讨论了8种情况下的最优解路径。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目链接:传送门

mmp 这题真的毒瘤 我比赛时候分的16种情况都没问题 死在边界上

题目大意:
有一个 n ∗ m n*m nm的地图,上面有 k k k个宝藏,有 q q q个安全列。现在你要用最小的步数收集所有的宝藏。要求:
1. 1. 1.不能往下走。
2. 2. 2.可以任意往左或往右走,但仅当在安全列上时才能往上走。
范围: 2 &lt; = n , m , k &lt; = 2 ∗ 1 0 5 , q &lt; = m 2&lt;=n,m,k&lt;=2*10^5,q&lt;=m 2<=n,m,k<=2105,q<=m

解题方法

由于不能往下走,所以一定是拿走每行的宝藏之后才能往上。
因此确定策略:
向左走收集完左边的宝藏,再向右走收集完右边的宝藏

向右走收集完右边的宝藏,再向左走收集完左边的宝藏
然后直接去一个安全列往上走。

不难发现,这样是没有后效性的,即珂以dp。
然后大力dp的复杂度好像是 O ( n q 2 ) O(nq^2) O(nq2)?显然过不了。
然后发现好像珂以单调队列优化,去掉一个 O ( q ) O(q) O(q)的复杂度,然而还是过不了QAQ。

于是手造几个图,发现到了最左边或最右边的宝藏后,只需要选择最近的一个安全列往上走就珂以了!
证明也比较显然,以走到最左边的宝藏且已经收集完这一行的所有宝藏为例:

令当前所在的行为 i i i,当前所在的列为 m i n l [ i ] minl[i] minl[i],当前所在的列左边第一个安全列编号为 p o s pos pos(即这一列为 b [ p o s ] b[pos] b[pos]),那么当前所在的列左边第二个安全列编号为 p o s − 1 pos-1 pos1(即这一列为 b [ p o s − 1 ] b[pos-1] b[pos1])。
如果从 m i n l [ i ] minl[i] minl[i]走到 b [ p o s − 1 ] b[pos-1] b[pos1]再往上走,那么肯定不如走到 b [ p o s ] b[pos] b[pos]再往上走优。
原因:从 m i n l [ i ] minl[i] minl[i]走到 b [ p o s − 1 ] b[pos-1] b[pos1]再往上走,与从 m i n l [ i ] minl[i] minl[i]走到 b [ p o s ] b[pos] b[pos]再往上走,再往左走到 b [ p o s − 1 ] b[pos-1] b[pos1]列,步数相同。
因为第 i i i列已经无法收集到宝藏,而走到 b [ p o s ] b[pos] b[pos]再往上走再往左走到 b [ p o s − 1 ] b[pos-1] b[pos1]列的走法有可能收集到上面一行的宝藏。
如果上面一行没有宝藏在第 b [ p o s ] b[pos] b[pos]列右端,那么走到 b [ p o s ] b[pos] b[pos]再往上走的走法是要往右多走一截的(这里最好画一些图帮助理解)。
如果上面一行有宝藏在 b [ p o s ] b[pos] b[pos]右端,也不难发现走到 b [ p o s − 1 ] b[pos-1] b[pos1]再往上走的走法不会比走到 b [ p o s ] b[pos] b[pos]再往上走的的走法更优。
因此选左边最近的安全列上去,是比选左边更远的安全列上去更优的。
右边同理。

于是策略变为:
每行走到最左端的宝藏处或者最右端的宝藏出,然后选左边最近的安全列和右边最近的安全列往上走。(这里左边和右边都要枚举一下)
然后就可以大力转移了qwq

时间复杂度: O ( n l o g q × 大 常 数 ) O(nlogq×大常数) O(nlogq×) O ( n ) O(n) O(n)扫,套上 O ( l o g q ) O(logq) O(logq)的二分找左边/右边最近的安全列)

另外:
比赛完我才发现,只要分8种情况就珂以了QAQ
现在很好奇我是怎么在比赛时16种情况一个不写错然后写错边界的qwq
8种情况(X = = = 左/右):
从X边走X边最近的安全列走到上面一行最X边的的宝藏再走到另一边的宝藏。
一共 2 ∗ 2 ∗ 2 = 8 2*2*2=8 222=8(种)情况qwq。

代码

注:前方高能!

#include<stdio.h>
#include<cstring>
#include<algorithm>
#include<math.h>
#include<vector>
#define re register int
#define rl register ll
using namespace std;
typedef long long ll;
ll read() {
	rl x=0,f=1;
	char ch=getchar();
	while(ch<'0' || ch>'9') {
		if(ch=='-')	f=-1;
		ch=getchar();
	}
	while(ch>='0' && ch<='9') {
		x=10*x+ch-'0';
		ch=getchar();
	}
	return x*f;
}
namespace I_Love {
 
const int Size=200005;
ll n,m,k,q,b[Size];
struct Treasure {
	ll r,c;
} w[Size];
inline bool comp(Treasure x,Treasure y) {
	if(x.r!=y.r)	return x.r<y.r;
	return x.c<y.c;
}
ll maxr[Size],minl[Size],sum[Size],up;
void Fujibayashi_Ryou() {
//	b[1]=1; b[2]=3; b[3]=6; b[4]=7;
//	printf("%d",b[upper_bound(b+1,b+1+4,5)-b-1]);
	n=read();
	m=read();
	k=read();
	q=read();
	for(re i=1; i<=k; i++) {
		w[i].r=read();
		w[i].c=read();
	}
	for(re i=1; i<=q; i++) {
		b[i]=read();
	}
	sort(b+1,b+1+q);
	sort(w+1,w+1+k,comp);
	b[q+1]=1e9;
	int now=1;
	//maxr表示一行最右边的宝藏的位置 minl表示一行最左边宝藏的位置
	for(re i=1; i<=n; i++) {
		while(now<=k && w[now].r<i)	now++;
		if(now>k || w[now].r>i) {
			//后面转移时,如果有一行没有宝藏,就相当于把这行删掉,然后答案+1
			maxr[i]=maxr[i-1];
			minl[i]=minl[i-1];
		} else {
			//sum[i]表示第i行有几个宝藏
			//up表示有宝藏的行中最上面的一行
			minl[i]=w[now].c;
			sum[i]=1;
			up=i;
			while(now<k && w[now+1].r==i) {
				now++;
				sum[i]++;
			}
			maxr[i]=w[now].c;
		}
	}
	//先统计第一行的答案
	ll prel=maxr[1]-1+(maxr[1]-minl[1]),prer=maxr[1]-1;
	ll ansl=9e18,ansr=9e18;
	//毒瘤 这里走到最上面有宝藏的一行就珂以了
	for(re i=2; i<=up; i++) {
		if(!sum[i]) {
			prel++;
			prer++;
			continue;
		} else {
			int pos=upper_bound(b+1,b+1+q,minl[i-1])-b-1;
			if(pos) {
				//上去,到右边再到左边 
				ansl=min(ansl,prel+minl[i-1]-b[pos]+1+abs(b[pos]-maxr[i])+(maxr[i]-minl[i]));
				//上去,到左边再到右边 
				ansr=min(ansr,prel+minl[i-1]-b[pos]+1+abs(b[pos]-minl[i])+(maxr[i]-minl[i]));
			}
			if(b[++pos]<=m) {
				//上去,到右边再到左边 
				ansl=min(ansl,prel+b[pos]-minl[i-1]+1+abs(b[pos]-maxr[i])+(maxr[i]-minl[i]));
				//上去,到左边再到右边 
				ansr=min(ansr,prel+b[pos]-minl[i-1]+1+abs(b[pos]-minl[i])+(maxr[i]-minl[i]));
			}
			pos=upper_bound(b+1,b+1+q,maxr[i-1])-b-1;
			if(pos) {
				//上去,到右边再到左边 
				ansl=min(ansl,prer+maxr[i-1]-b[pos]+1+abs(maxr[i]-b[pos])+(maxr[i]-minl[i]));
				//上去,到左边再到右边 
				ansr=min(ansr,prer+maxr[i-1]-b[pos]+1+abs(minl[i]-b[pos])+(maxr[i]-minl[i]));
			}
			if(b[++pos]<=m) {
				ansl=min(ansl,prer+b[pos]-maxr[i-1]+1+abs(maxr[i]-b[pos])+(maxr[i]-minl[i]));
				
				ansr=min(ansr,prer+b[pos]-maxr[i-1]+1+abs(b[pos]-minl[i])+(maxr[i]-minl[i]));
			}
		}
		prel=ansl,prer=ansr;
//		printf("lans=%lld rans=%lld\n",ansl,ansr);
		ansl=ansr=9e18;
	}
	printf("%I64d",min(prel,prer));
}
 
}
int main() {
	I_Love::Fujibayashi_Ryou();
	return 0;
}
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值