LuoguP1514引水入城(搜索+dp)

https://www.luogu.org/problemnew/show/P1514
题目如上
非常非常巧妙的一道题目;
1.先思考,要求的是什么?
如题目所说,目的是要把第n排全部建上那个什么水利工程,全建上,也就是全部覆盖!再考虑起点,也就是河岸,动动笔,也不难发现,某些节点可以拓展到终点的一片区域,丑图
如样例一中9可以拓展到的点如图所示,

绿色的框则表示9,也就是入口第一列的这个点所能到达的出口范围,这里叫做他能管辖的范围;

理解到这一步,应该很明显了,要求覆盖所有终点所需要的起点最少,故覆盖完终点所需要的管辖区间最少。

over 完美的变成了区间覆盖问题!

不了解区间覆盖问题的可以参考
https://blog.csdn.net/karry_zzj/article/details/70886931
2.如何求解??

这里用的贪心,但我用的动态规划;
设f[i][j]为覆盖完区间i–j所需要最少的起点数,dp[i]表示覆盖完1–i所需要最少的起点数,故dp[m]为最终答案;
每个起点所管辖的区域如何求呢?
非常明显,搜索大法—BFS!!你想非主流用DFS也没人管你。。。

每个第一行的点开始宽搜,搜到最后一行也就是终点更新管辖区域。
那么问题也就迎刃而解了NICE!!!IG,NB!!!!

代码注释也不少,应该较好理解

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<queue>
#include<algorithm>
using namespace std;
int n,m,dp[510],f[510][510],e[510][510],out[510],vis[510][510],cx[5]={0,-1,1,0,0},cy[5]={0,0,0,-1,1};
struct IN{//  e存地图,out[i]表示最后一行第i列能否被覆盖到,vis用来BFS判重。 
	int l,r,v;
}I[510];//第i个起点的管辖区域l--r,v表示该七点是否被其他起点所搜到,如果被其他起点能到达,该起点就没暖用了。。 
struct Node{int x,y;};
void BFS(int sy){
	memset(vis,0,sizeof(vis));
	queue<Node> q;
	if(n==1) out[sy]=1,I[sy].l=I[sy].r=sy;//特别注意输入只有一行的情况,起点也是终点 
	q.push((Node){1,sy});
	while(!q.empty()){
		Node t=q.front(); q.pop();
		for(int i=1;i<=4;i++){
			int xx=t.x+cx[i],yy=t.y+cy[i];
			if(e[xx][yy]<e[t.x][t.y]){//可以拓展 
				if(xx==1) I[yy].v=0;//第yy个节点可以被别的拓展到,没p用了byebye!! 
				if(xx==n){
					out[yy]=1;
					if(yy<I[sy].l) I[sy].l=yy;
					if(yy>I[sy].r) I[sy].r=yy;
				}//更新管辖区域 
				if(!vis[xx][yy]) q.push((Node){xx,yy}),vis[xx][yy]=1;
			}
		}
	}
}
int main(){
	scanf("%d%d",&n,&m);
	memset(e,0x7f,sizeof(e));//初始化防止边界问题 
	for(int i=1;i<=n;i++) for(int j=1;j<=m;j++) scanf("%d",&e[i][j]);
	for(int i=1;i<=m;i++) I[i].v=1,I[i].l=m+1,I[i].r=0;//管辖区域的初始化,因为要逐渐扩大,右端点要靠最左,同理...... 
	for(int i=1;i<=m;i++) if(I[i].v) BFS(i);//BFS每一个节点 
	int sum=0;
	for(int i=1;i<=m;i++) if(!out[i]) sum++;
	if(sum){ printf("%d\n%d\n",0,sum);return 0;}//判断特例,不可能覆盖完 
 	memset(dp,0x7f,sizeof(dp));
	memset(f,0x7f,sizeof(f)); 
	for(int k=1;k<=m;k++){
		for(int i=I[k].l;i<=I[k].r;i++){
			for(int j=i;j<=I[k].r;j++){
				if(i==1) dp[j]=1;
				f[i][j]=1;
			}
		}
	}//初始化 
	for(int i=1;i<=m;i++) for(int j=1;j<i;j++) dp[i]=min(dp[i],dp[j]+f[j+1][i]);//dp开始! 
	printf("%d\n%d\n",1,dp[m]);//over!
	return 0;//IG NB!!!
}

就喜欢短小精悍的代码,关注一下谢谢了!!!

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值