海码3月月赛总结

三月月赛 十分有趣,里面的题十分有意思,大家可以来做一做,我也在这里做一个总结和对各题的解法做一个说明。

奥小白和小南老师逛菜市场

算法分析

本题的主要算法是贪心,不难理解。通过对上,下,左,右四个方向进行遍历,看看哪个点的值是最小的。遍历的时候可以采用深搜+回溯的方法(在搜索学习心得中有详细介绍深搜)或递推的方法,今天我们来重点讲一下递推的方法来解这道题。

代码及易错点分析

首先看一下输入和初始化:

	memset(stall,0x7f,sizeof(stall));
	cin>>n>>m>>k;
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=m;j++)
		{
			cin>>stall[i][j];//存放每个摊位的数值。
		}
	}

小巧玲珑的输入,下面我们来介绍易错的递推部分。

先看一段代码:

	for(int i=1;i<k;i++)
	{
		sum+=mmin;
		mmin=INF;//INF=0x7fffffff,代表在最大值,mmin是最小值。
		int ta,tb;//记录现在的坐标
		if(stall[posa-1][posb]<mmin)//posa,posb表示上一个坐标
		{
			ta=posa-1;
			tb=posb;
			mmin=stall[posa-1][posb];
		}
		if(stall[posa][posb-1]<mmin)
		{
			ta=posa;
			tb=posb-1;
			mmin=stall[posa][posb-1];
		}
		if(stall[posa+1][posb]<mmin)
		{
			ta=posa+1;
			tb=posb;
			mmin=stall[posa+1][posb];
		}
		if(stall[posa][posb+1]<mmin)
		{
			ta=posa;
			tb=posb+1;
			mmin=stall[posa][posb+1];
		}
		posa=ta;
		posb=tb;
	}

一个大佬看到这样的代码肯定会不屑一顾,错误很多(虽然样例能过。。。)。首先如果对于某个点来说上,下,左,右四个方向的点都早已走过,但却还没到限定的步数,照题意来说应该停下不继续走了。但这个代码并没有这个功能,来修改一下。

	for(int i=1;i<k;i++)
	{
		sum+=mmin;
		vis[posa][posb]=1;
		mmin=INF;
		int ta,tb;
		if(stall[posa-1][posb]<mmin&&!vis[posa-1][posb])
		{
			ta=posa-1;
			tb=posb;
			mmin=stall[posa-1][posb];
		}
		if(stall[posa][posb-1]<mmin&&!vis[posa][posb-1])
		{
			ta=posa;
			tb=posb-1;
			mmin=stall[posa][posb-1];
		}
		if(stall[posa+1][posb]<mmin&&!vis[posa+1][posb])
		{
			ta=posa+1;
			tb=posb;
			mmin=stall[posa+1][posb];
		}
		if(stall[posa][posb+1]<mmin&&!vis[posa][posb+1])
		{
			ta=posa;
			tb=posb+1;
			mmin=stall[posa][posb+1];
		}
		if(mmin==INF)//如果未被更新,结束代码
		{
			cout<<sum;
			return 0;
		}
		posa=ta;
		posb=tb;
	}

这个代码看起来已经是完美无缺了,但其实还有一个错误没又发现。这里就不得不提一下 m e m s e t ( s t a l l , 0 x 7 f , s i z e o f ( s t a l l ) ) ; memset(stall,0x7f,sizeof(stall)); memset(stall,0x7f,sizeof(stall)); 这个语句。这个语句的作用是把 s t a l l stall stall数组中的数设置为
0 x 7 f 7 f 7 f 7 f 0x7f7f7f7f 0x7f7f7f7f,是先把“0x7f”转成十六进制,再在每个数位上更新数字,所以 m e m s e t ( a , 1 , s i z e o f ( a ) ) memset(a,1,sizeof(a)) memset(a,1,sizeof(a));不能把数组里的每个数字变成1。言归正传, 0 x 7 f 7 f 7 f 7 f < 0 x 7 f f f f f f f ( I N F ) 0x7f7f7f7f<0x7fffffff(INF) 0x7f7f7f7f<0x7fffffff(INF)所以 i f ( m m i n = = I N F ) if(mmin==INF) if(mmin==INF)这个语句不会成立。所以有两种改法。见下。

第一种改法(改递推部分):

	for(int i=1;i<k;i++)
	{
		sum+=mmin;
		vis[posa][posb]=1;
		mmin=INF;
		int ta,tb;
		if(stall[posa-1][posb]<mmin&&!vis[posa-1][posb]&&posa>1)//加上边界判断,下同
		{
			ta=posa-1;
			tb=posb;
			mmin=stall[posa-1][posb];
		}
		if(stall[posa][posb-1]<mmin&&!vis[posa][posb-1]&&posb>1)
		{
			ta=posa;
			tb=posb-1;
			mmin=stall[posa][posb-1];
		}
		if(stall[posa+1][posb]<mmin&&!vis[posa+1][posb]&&posa<n)
		{
			ta=posa+1;
			tb=posb;
			mmin=stall[posa+1][posb];
		}
		if(stall[posa][posb+1]<mmin&&!vis[posa][posb+1]&&posb<n)
		{
			ta=posa;
			tb=posb+1;
			mmin=stall[posa][posb+1];
		}
		if(mmin==INF)
		{
			cout<<sum;
			return 0;
		}
		posa=ta;
		posb=tb;
	}

第二种改法(改初始化):

	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=m;j++)
		{
			stall[i][j]=0x7fffffff;
		}
	}

华丽丽地结束了。

总结

本题的思路十分清晰简单,也不用考虑时间和空间复杂度,但涉及到的细节问题很多,大概是提高第一题/普及第一二题的难度,这种题如果丢分是十分可惜的。

奥小白与小南老师放烟花

这题笔者看到时,心中是有些崩溃的。因为每一次碰壁,有8种情况要讨论。。。手写if要写死的。。。。再看了一眼数据范围,感觉打表也不太行。。。。怎么办呢???如果是在竞赛里,想不出来,只能分8类讨论(千万不要打表,要死的!)。代码如下,没什么技术含量,但对耐心有很大考验。

void update()
{
	//dir1,dir2表示方向
	//ta,tb表是现在的方位。
	int ta=posa,tb=posb;
	if(way)
	{
		num++;
		way=!way;
		if(posa==1&&posb!=1&&dir1==-1&&dir2==-1)
		{
			posb--;
			dir1=1;
			dir2=-1;
		}
		else if(posa==n&&dir1==1&&dir2==1)
		{
			posb++;
			dir1=-1;
			dir2=1;
		}
		else if(posb==1&&posa!=1&&dir1==-1&&dir2==-1)
		{
			posa--;
			dir1=-1;
			dir2=1;
		}
		else if(posb==m&&dir1==1&&dir2==1)
		{
			posa++;
			dir1=1;
			dir2=-1;
		}
		else
		{
			num--;
			posa+=dir1;
			posb+=dir2;
			way=!way;
		}
	}
	else
	{
		num++;
		way=!way;
		if(posa==1&&posb!=1&&dir1==-1&&dir2==1)
		{
			posb++;
			dir1=dir2=1;
		}
		else if(posa==n&&dir1==1&&dir2==-1)
		{
			posb--;
			dir1=dir2=-1;
		}
		else if(posb==1&&posa!=1&&dir1==1&&dir2==-1)
		{
			posa++;
			dir1=dir2=1;
		}
		else if(posb==m&&dir1==-1&&dir2==1)
		{
			posa--;
			dir1=dir2=-1;
		}
		else
		{
			num--;
			posa+=dir1;
			posb+=dir2;
			way=!way;
		}
	}
	//终于结束了。。。。
}

这段代码花的时间不多,45分钟左右。。。目前笔者还没有想出其他更易懂的方法了,如有想法,可以跟我沟通。

奥小白与小南老师享受大餐

思路

这道题其实是技巧性非常强的一道题,一般思路是把区域内的数排序后一个一个看是否可行,时间复杂度是O(mn log n),会爆5-6个点。那如何优化一下呢?

这个题的可优化的部分就是在排序的次数。排序是为了找出满足 “最好吃的那道,不能比稍微差一点点的两道加起来难吃” 这让我不禁想起了一个永远无法满足要求的数列:
斐波那契数列 那如果我再在这个数列里加入任意一个数,是不是就满足要求了?题目中说到美味度<2^31,在int范围内,对于斐波那契数列来说大约有60项,所以只要
Ri-Li>=60; 那么答案就是YES,否则进行排序。

代码

#include <iostream>
#include <algorithm>
using namespace std;
long long a[200000];
long long b[100];
int main()
{
	int n, m;
	cin >> n >> m;
	for (int i = 1; i <= n; ++i)
		cin >> a[i];
	for (int i = 1; i <= m; ++i)
	{
		int l, r;
		cin >> l >> r;
		if (r - l + 1 >= 60)
		{
			cout << "YES" << endl;
		}
		else
		{
			bool flag = false;
			int c = r - l + 1;
			for (int j = l; j <= r; ++j)
			{
				b[j - l] = a[j];
			}
			sort(b, b + c);
			for (int j = 0; j < c - 2&&!flag; ++j)
				if (b[j] + b[j + 1] > b[j + 2])
					flag = true;
			if (flag)
				cout << "YES" << endl;
			else
				cout << "NO" << endl;
		}
	}
return 0;
}

总结

这道题很有意思,如果没有思路也能骗几分,但如果思路有了,题目也迎刃而解了。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值