Coderorces 1201D

这篇博客探讨了一种棋盘问题,其中宝藏分布在不同行的指定列上,玩家只能沿着安全列移动。博主详细阐述了如何通过动态规划计算出收集所有宝藏的最少步数,涉及了行转移、端点策略和安全列的维护。算法复杂度较高,需要分类讨论多个状态转移情况。

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

给定一个nm列的棋盘,在上面有k个宝藏,一开始在点(1,1),在i行可以自由的向左或向右走,但是只能在q个给定的“安全列”向i+1行走,问收集所有宝藏至少需要多少步数?

对于每一行,无论初始在哪里,最少的收集完当前行的策略应该是最后收集左右端点的策略,并且行只能向更高行走,也就是i行的答案可以由i-1行得来,所以可以考虑dp,设f[i][0],f[i][1]分别为收集完前i行,最后站在这个一行的左端点处和右端点处的最少步数,那么现在的位置是可以由上一行的两个端点处走过来的,因此f[i][j]=min(f[i-1][0]+step_{1},f[i-1][1]+step_{1})

step_{1}是从上一行的左端点处到目前为止的步数,另外一个则是右端点的。

同时注意到从一个端点移动到上一行应该是先移动到最近的“安全列”所以step_{1}又有两种情形,向左和向右各有一个最近的安全列,这里最近应该是一个方向上最近,一个方向上经过了一个安全列而不想上走一定是浪费步数的,所以需要维护一个当前列向左向右的最近安全列,这里利用dp也是容易得到的,设safe[i]为i列是否为安全列,那么从右向左遍历列,如果当前列是安全列,那么向右的最近安全列就是自己,如果不是,那么向右的最近安全列就是右边一个元素的最近向右安全列位置,即safe\_r[i]=safe[i]?i:safe\_r[i+1],向左的同理,只不过向左的是正向枚举。同时也需要维护一行的左端点是谁,端点的意思是最左/右的宝藏,为了方便更新,由于向左的宝藏是记录最小的坐标,初始值应该是inf,向右的记录最大,初始值则是0。

这道题有很多细节,第一,需要注意起始行,先初始化起始行,在遍历其他行,而起始行不一定是第一行;第二,某一行可能没有宝藏,所以i行可能不是i-1行转移来的,应该是最近的有宝藏的行来的,所以当前行没有宝藏的时候,需要维护一个变量gap来表示距离有宝藏的行有多远,因此最后的转移方程是f[i][j]=min(f[i-gap][0]+step_{1},f[i-gap][1]+step_{1});第三,step的值有三部分,“上一行的端点到最近的安全列的距离”,“安全列上移动的距离”(这个距离就是gap),“从安全列到达当前行后,到达这一行的端点的步数"。

(有没有宝藏就看l[i]==inf或者r[i]==0就可以了,两个同步,任意选择一个即可)

这题里要分类清楚,每个f[i][j]从上一行的两个端点来,每个端点可以向左走也可以向右走,所以一行需要分类讨论8个点,j的值{0,1}两种,上一行两个端点两种,端点行走方向两种,共八种。转移种数太多,难度不比模拟小。

最后,自最高层向下遍历,如果当前i行有宝藏,那么min(f[i][0],f[i][1]),自高向下遍历确保第一个找到的有宝藏的行是最后一行。

#include<bits/stdc++.h>
using namespace std;

int read()
{
	int ret=0,base=1;
	char ch=getchar();
	while(!isdigit(ch))
	{
		if(ch=='-') base=-1;
		ch=getchar();
	}
	while(isdigit(ch))
	{
		ret=(ret<<3)+(ret<<1)+ch-48;
		ch=getchar();
	}
	return ret*base;
}

const int inf=0x3fffffff;
const long long INF=0x3f3f3f3f3f3f3f;

int n,m,k,q;
long long f[300005][2];
int l[300005],r[300005];
int ll[300005],rr[300005];
int safe_l[300005],safe_r[300005];
bool safe[200005];

int solve(int i,int k,int p)
{
	//在i行k列上,最后停在p,需要走多少步
	if(p==0)//停在最左
	{
		if(k<=r[i]) return r[i]-k+r[i]-l[i];
		return k-l[i];
	}
	else
	{
		if(k>=l[i]) return k-l[i]+r[i]-l[i];
		return r[i]-k;
	}
}

int main()
{
	n=read();m=read();
	for(int i=1;i<=n;i++) l[i]=inf;
	k=read();q=read();
	for(int i=1;i<=k;i++)
	{
		//每一行最靠左或最靠右的
		int x=read(),y=read();
		l[x]=min(l[x],y);
		r[x]=max(r[x],y);
	}
	for(int i=1;i<=q;i++) safe[read()]=true;
	for(int i=m;i>=1;i--)
	{
		if(safe[i]) safe_r[i]=i;
		else safe_r[i]=safe_r[i+1];
	}
	for(int i=1;i<=m;i++)
	{
		if(safe[i]) safe_l[i]=i;
		else safe_l[i]=safe_l[i-1];
	}
	for(int i=1;i<=n;i++)  f[i][0]=f[i][1]=INF;
	int begin=1;
	for(int i=1;i<=n;i++)
	{
		if(r[1])
		{
			f[i][0]=r[i]-1+r[i]-l[i]+i-1;
			f[i][1]=r[i]-1+i-1;
			begin=2;
			break;
		}
		if(r[i])
		{
			if(safe[1])
			{
				f[i][0]=i-1+solve(i,1,0);
				f[i][1]=i-1+solve(i,1,1);
			}
			else if(safe_r[1])
			{
				int pos=safe_r[1];
				f[i][0]=min(f[i][0],(long long)(pos-1+i-1+solve(i,pos,0)));
				f[i][1]=min(f[i][1],(long long)(pos-1+i-1+solve(i,pos,1)));
			}
			begin=i+1;
			break;
		}
	}
	for(int i=begin,gap=1;i<=n;i++)
	{
		//本行没宝藏
		if(!r[i]) gap++;
		//本行有宝藏
		//f[i][0]
		else
		{
			//从f[i-1][0]转移来
			if(safe_r[l[i-gap]])//如果上一行的最左有向右的安全列
			{
				int pos=safe_r[l[i-gap]];
				f[i][0]=min(f[i][0],f[i-gap][0]+solve(i,pos,0)+pos-l[i-gap]+gap);
			}
			if(safe_l[l[i-gap]])//如果上一行的最左有向左的安全列
			{
				int pos=safe_l[l[i-gap]];
				f[i][0]=min(f[i][0],f[i-gap][0]+solve(i,pos,0)+l[i-gap]-pos+gap);
			}
			//从f[i-1][1]转移来
			if(safe_r[r[i-gap]])
			{
				int pos=safe_r[r[i-gap]];
				f[i][0]=min(f[i][0],f[i-gap][1]+solve(i,pos,0)+pos-r[i-gap]+gap);
			}
			if(safe_l[r[i-gap]])
			{
				int pos=safe_l[r[i-gap]];
				f[i][0]=min(f[i][0],f[i-gap][1]+solve(i,pos,0)+r[i-gap]-pos+gap);
			}
		//f[i][1]
			//从f[i-1][0]来
				//走右边的安全列
			if(safe_r[l[i-gap]])
			{
				int pos=safe_r[l[i-gap]];
				f[i][1]=min(f[i][1],f[i-gap][0]+solve(i,pos,1)+pos-l[i-gap]+gap);
			}
				//走zuo边的安全列
			if(safe_l[l[i-gap]])
			{
				int pos=safe_l[l[i-gap]];
				f[i][1]=min(f[i][1],f[i-gap][0]+solve(i,pos,1)+l[i-gap]-pos+gap);
			}
			//从f[i-1][1]来
			if(safe_r[r[i-gap]])
			{
				int pos=safe_r[r[i-gap]];
				f[i][1]=min(f[i][1],f[i-gap][1]+solve(i,pos,1)+pos-r[i-gap]+gap);
			}
			if(safe_l[r[i-gap]])
			{
				int pos=safe_l[r[i-gap]];
				f[i][1]=min(f[i][1],f[i-gap][1]+solve(i,pos,1)+r[i-gap]-pos+gap);
			}
			gap=1;//记得重置gap
		}
	}
	for(int i=n;i>=1;i--)
	{
		if(r[i])
		{
			printf("%lld\n",min(f[i][0],f[i][1]));
			return 0;
		}
	}
 	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值