三月月赛 十分有趣,里面的题十分有意思,大家可以来做一做,我也在这里做一个总结和对各题的解法做一个说明。
奥小白和小南老师逛菜市场
算法分析
本题的主要算法是贪心,不难理解。通过对上,下,左,右四个方向进行遍历,看看哪个点的值是最小的。遍历的时候可以采用深搜+回溯的方法(在搜索学习心得中有详细介绍深搜)或递推的方法,今天我们来重点讲一下递推的方法来解这道题。
代码及易错点分析
首先看一下输入和初始化:
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;
}
总结
这道题很有意思,如果没有思路也能骗几分,但如果思路有了,题目也迎刃而解了。