洛谷 P1514 引入水域(贪心线段覆盖 DP)

23 篇文章 0 订阅
1 篇文章 0 订阅

题目大意:

在一个n*m棋盘中,我们把源放在第一行,源可以对外进行BFS种子填充,问:为了使得最后一行被填充完毕,我们最少需要在第一行放多少个源。假如最后一行不能被填充,问我们不能填充的个数。

解题思路:

首先,最后一行不能被填充十分简单,我们只需要在第一行逐列放源,假如最后一行还是有不能被覆盖的点的话,证明不能被填充完毕。

现在关键是第一问,最少放几个源。

结论1:当我们已知能够完全覆盖最后一行时。当源开始做flood fill,假若能覆盖到最后一行,那么覆盖到的最后一行必定是一个完整的区间(即不会产生覆盖了 [ 0,3]  [5 ,7]的情况)可以用反证法证明。https://www.cnblogs.com/Tony-Double-Sky/p/9871976.html

所以,当我们可以求出第一行中每一列的能到达最后一列的区间的范围,然后我们判断最少需要多少个区间就能把最后一行覆盖。

首先,我们求第一行的每一列的所属区间范围。最简单的方法是,我们重复做BFS,但是复杂度过了,最后会T。

这里我们用DP的方法。

for nr in r_c_neighbour

memol[r][c]=min(memol[nr][nc])

其中memol[r][c]表示r,c这个点能够到达的最后一行的最左边的点的位置。nr,nc表示r,c的四邻域

同样的

for nr in r_c_neighbour

memor[r][c]=max(memor[nr][nc])

其中memor[r][c]表示r,c这个点能够到达的最后一行的最右边的点的位置。nr,nc表示r,c的四邻域

那这里还有一个问题,边界是什么:

很显然,当我们走到最后一行时,若某一格不能再DFS下去了,那么我们认为这是我们能走的最后一个位置。

写的时候注意:

(1)首先有一些点可能是到达不了最后一行的,注意这些点的状态的赋值。不要因为错误的赋值,使得转移方程出错

(2)在DFS中,我们很重要的一环是防止走重复,在这里我们注意有两种走重复,一种是memo已经赋值的了,那么我们返回memo即可,另外则是memo还未赋值,这时候我们同样需要返回。

关于区间覆盖:

这是一种贪心的做法,我们从最左边的端点开始,要保证能够把最左边的点覆盖。然后,我们选择能够覆盖最左边的点中的区间能够达到最右的区间,每次都这样贪心选取。

#include <bits/stdc++.h>
using namespace std;
const int MAXN=510;
int chess[MAXN][MAXN];
pair<int,int> memo[MAXN][MAXN];
int vis[MAXN][MAXN];
int n,m;
const int dr[]={0,-1,0,1};
const int dc[]={-1,0,1,0};
pair<int,int> dfs(int r,int c){
	if(memo[r][c].first!=-1){
		vis[r][c]=1;
		return memo[r][c];}
	if(vis[r][c]==1)return make_pair(-1,-1);
	vis[r][c]=1;
	int fir=1e9;
	int sec=-1;
	for(int i=0;i<4;i++){
		int nr=r+dr[i];
		int nc=c+dc[i];
		if(nr<0||nr==n || nc<0||nc==m)continue;
		if(chess[nr][nc]>=chess[r][c])continue;
		pair<int,int> ret=dfs(nr,nc);
		if(ret.first==-1)continue;
		fir=min(ret.first,fir);
		sec=max(ret.second,sec);
	}
	memo[r][c].first=fir;
	memo[r][c].second=sec;
	if(r==n-1 && memo[r][c].first>c)memo[r][c].first=c;
	if(r==n-1 && memo[r][c].second<c)memo[r][c].second=c;
	return memo[r][c];
}
int main(){
	cin>>n>>m;
	for(int i=0;i<n;i++)
		for(int j=0;j<m;j++){
			cin>>chess[i][j];
		}
	for(int i=0;i<n;i++)
		for(int j=0;j<m;j++){
			memo[i][j].first=memo[i][j].second=-1;
		}
	memset(vis,0,sizeof(vis));
	for(int i=0;i<m;i++){
		dfs(0,i);
	}
	int flag=0;
	int cnt=0;
	for(int i=0;i<m;i++){
		if(vis[n-1][i]==0){
			flag=1;
			cnt++;
		}
	}
	if(flag){
		cout<<0<<endl;
		cout<<cnt<<endl;
		return 0;
	}
	int lf=0;
	cout<<1<<endl;
	cnt=0;
	while(lf<=m-1){
		cnt++;
		int maxr=-1;
		for(int i=0;i<m;i++){
			if(memo[0][i].first==1e9)continue;
			if(memo[0][i].first<=lf){
				maxr=max(maxr,memo[0][i].second);
			}
		}
		assert(maxr!=-1);
		lf=maxr+1;
	}
	cout<<cnt<<endl;
	return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值