引水入城,巧妙搜索

抛出问题

【简化题面】给定一个\(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;
}

参考文献

  1. tsukasa. 题解 引水入城. https://www.luogu.org/problemnew/solution/P1514, 2017-10-23

写在最后

因为题目真不难,所以博主偷偷懒就写了这么点,所以各位抱歉啦。不过我相信大家看通俗易懂的代码还是看得懂的吧(逃)。
感谢参考文献中提到的文献的帮助。
大多数内容为个人智力成果,如需转载,请注明出处,禁止作商业用途传播。
最后,感谢各位的阅读。

posted @ 2018-09-10 22:17  孤独·粲泽  阅读(160)  评论(0编辑  收藏  举报