引水入城,巧妙搜索
抛出问题
【简化题面】给定一个\(N\times M\)的矩阵,每个元素代表一个城市,每个城市都有一定的海拔。由于最上面一行临近湖泊,可以建蓄水厂,其它的城市之间只能通过输水站来通水,且只能从海拔高的城市输向海拔低的城市。
目前面临的问题是,最下面那一行的城市临近沙漠,极度缺水,必须全部通水。如果能通水,单独输出一行\(1\),再输出一行最少需要修建的蓄水厂的数量;如果不能通水,单独输出一行\(0\),再输出一行不能通水的临近沙漠的城市的数量。
解决问题
闲谈
在解决问题之前,我们先闲谈一下。昨天没睡好,今天看一会电脑就眼睛疼头疼,还想睡觉。然后做这道题的时候就是晕晕的~。真的不睡好觉就连简单题也不会做,要是发生在NOIP就...总之,我还是AC了这道题(虽然厚颜无耻看了别人优秀的题解)。要我自己做的话,肯定要慢几十倍,2333。
暴力法
枚举所有状态即可。明摆着搜索还简单些为什么一定要暴力呢?
一般的搜索法
这里介绍一下我一开始要写的搜索。
我的方法就是,每个可建造蓄水厂的城市都试造一下蓄水厂,然后看看造了蓄水厂之后能覆盖多少个极度缺水城市。接着最少线段覆盖就很简单了,还有判断是否还有没过到水的极度缺水城市也很简单,搜索的时候记录一下,最后扫一遍即可。
这种做法说起来好像就这么点,而实际做起来也只有这么点2333。问题就出在每次的“试造”。这样我们每次都要初始化判定是否来过的数组,复杂度不小。
优秀的搜索法
实际上,我们只需要搜索一遍就够了!为什么?因为“看看造了蓄水厂之后能覆盖多少个极度缺水城市”是可以递推的!于是就优化了很多。
没了?没了。
众人:博主水博客!!!
博主:简单得不好怎么说,看代码吧,很容易看懂的。
众人:懒博主!
贴代码
//引水入城,打瞌睡zzzZZ
#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;
bool che[510][510];
int l[510][510], r[510][510], N, M, y[510][510];
void dfs(int i, int j)
{
if(!i || !j || i > N || j > M)
return;
if(!che[i][j])
{
che[i][j] = true;
//In here can we use 'for' with an array to simplify the program.我们可以用for循环简化程序
if(y[i][j] > y[i+1][j])
{
dfs(i+1, j);//记得先搜再递推,这样才有值(回溯嘛)。
l[i][j] = min(l[i][j], l[i+1][j]);
r[i][j] = max(r[i][j], r[i+1][j]);
}
if(y[i][j] > y[i][j+1])
{
dfs(i, j+1);
l[i][j] = min(l[i][j], l[i][j+1]);
r[i][j] = max(r[i][j], r[i][j+1]);
}
if(y[i][j] > y[i-1][j])
{
dfs(i-1, j);
l[i][j] = min(l[i][j], l[i-1][j]);
r[i][j] = max(r[i][j], r[i-1][j]);
}
if(y[i][j] > y[i][j-1])
{
dfs(i, j-1);
l[i][j] = min(l[i][j], l[i][j-1]);
r[i][j] = max(r[i][j], r[i][j-1]);
}
}
}
int main()
{
int cnt = 0;
scanf("%d%d", &N, &M);
for(register int i = 1; i <= N; i += 1)
for(register int j = 1; j <= M; j += 1)
scanf("%d", &y[i][j]);
memset(l, 0x3f, sizeof l);
for(register int i = 1; i <= M; i += 1)
l[N][i] = r[N][i] = i;
for(register int i = 1; i <= M; i += 1)
if(!che[1][i])
dfs(1, i);
bool flag = false;
for(register int i = 1; i <= M; i += 1)
if(!che[N][i])
{
flag = true;
cnt += 1;
}
if(flag)
{
printf("0\n%d", cnt);
return 0;
}
int okay = 1, maxr;
while(okay <= M)
{
maxr = 0;
for(register int i = 1; i <= M; i += 1)
if(l[1][i] <= okay)
maxr = max(maxr, r[1][i]);
cnt += 1;
okay = maxr + 1;
}
printf("1\n%d", cnt);
return 0;
}
参考文献
- tsukasa. 题解 引水入城. https://www.luogu.org/problemnew/solution/P1514, 2017-10-23
写在最后
因为题目真不难,所以博主偷偷懒就写了这么点,所以各位抱歉啦。不过我相信大家看通俗易懂的代码还是看得懂的吧(逃)。
感谢参考文献中提到的文献的帮助。
大多数内容为个人智力成果,如需转载,请注明出处,禁止作商业用途传播。
最后,感谢各位的阅读。