[NOIP2010]引水入城

[NOIP2010]引水入城
题目描述
在一个遥远的国度,一侧是风景秀美的湖泊,另一侧则是漫无边际的沙漠。该国的行政区划十分特殊,刚好构成一个N 行M 列的矩形,如上图所示,其中每个格子都代表一座城市,每座城市都有一个海拔高度。

为了使居民们都尽可能饮用到清澈的湖水,现在要在某些城市建造水利设施。水利设施有两种,分别为蓄水厂和输水站。蓄水厂的功能是利用水泵将湖泊中的水抽取到所在城市的蓄水池中。
因此,只有与湖泊毗邻的第1 行的城市可以建造蓄水厂。而输水站的功能则是通过输水管线利用高度落差,将湖水从高处向低处输送。故一座城市能建造输水站的前提,是存在比它海拔更高且拥有公共边的相邻城市,已经建有水利设施。由于第N 行的城市靠近沙漠,是该国的干旱区,所以要求其中的每座城市都建有水利设施。那么,这个要求能否满足呢?如果能,请计算最少建造几个蓄水厂;如果不能,求干旱区中不可能建有水利设施的城市数目。
输入输出格式
输入格式:
输入文件的每行中两个数之间用一个空格隔开。输入的第一行是两个正整数N 和M,表示矩形的规模。接下来N 行,每行M 个正整数,依次代表每座城市的海拔高度。
输出格式:
输出有两行。如果能满足要求,输出的第一行是整数1,第二行是一个整数,代表最少建造几个蓄水厂;如果不能满足要求,输出的第一行是整数0,第二行是一个整数,代表有几座干旱区中的城市不可能建有水利设施。
输入输出样例
输入样例#1:
【输入样例1】2 59 1 5 4 38 7 6 1 2【输入样例2】3 68 4 5 6 4 47 3 4 3 3 33 2 2 1 1 2
输出样例#1:
【输出样例1】11【输出样例2】13
说明
【样例1 说明】
只需要在海拔为9 的那座城市中建造蓄水厂,即可满足要求。
【样例2 说明】

上图中,在3 个粗线框出的城市中建造蓄水厂,可以满足要求。以这3 个蓄水厂为源头
在干旱区中建造的输水站分别用3 种颜色标出。当然,建造方法可能不唯一。
【数据范围】

题解:
一个比较难以想到的结论:在有解的情况下每个点所能到达的第n层的点的集合一定为一段区间。
证明:若所能到达的点不为一段即中间有一段区间无法到达那么必然这个点所能到达的点的集合一定能把不能到达的点围起来(不然他怎么到另一边的),但是要到中间的那个区间必然要经过这些点,既然这些点都无法到达,那么就无解。
所以可以先bfs出每个点所控制的区间,进行区间dp即可。

代码:
#include<bits/stdc++.h>
using namespace std;

const int max_n = 510;
const int inf   = 1e9+7;

int wh[4][4]={{0,1},{1,0},{-1,0},{0,-1}};
int f[max_n][max_n],a[max_n][max_n];
int l_res[max_n][max_n],r_res[max_n][max_n];
bool vis[max_n][max_n];
int n,m,l,r,ans;

inline void init()
{
	for(int i=1; i<=m; ++i)
	  for(int j=1; j<=i; ++j)
	    f[j][i]=inf;
	memset(l_res,0x7f,sizeof(l_res));
}

inline bool check()
{
	for(int i=1; i<=m; ++i)
	  if(!vis[n][i]) ans++;
	return ans!=0;
}

void dfs(int nx,int ny)
{
	if(vis[nx][ny])
	{
		l=min(l_res[nx][ny],l);
		r=max(r_res[nx][ny],r);
		return;
	}
	vis[nx][ny]=true;
	for(int i=0; i<4; ++i)
	{
		int tx=nx+wh[i][0],ty=ny+wh[i][1];
		if(tx>0 && ty>0 && tx<=n && ty<=m && a[nx][ny]>a[tx][ty])
		{
			dfs(tx,ty);
			l_res[nx][ny]=min(l_res[nx][ny],l_res[tx][ty]);
			r_res[nx][ny]=max(r_res[nx][ny],r_res[tx][ty]);
		} 
	}
	if(nx==n) 
	{
		l_res[nx][ny]=min(ny,l_res[nx][ny]);
		r_res[nx][ny]=max(ny,r_res[nx][ny]);
		l=min(l,ny);
		r=max(r,ny);
	}
}

inline void dp()
{
	for(int i=1; i<m; ++i)
	  for(int j=1; j+i<=m; ++j)
	    for(int k=j; k<j+i; ++k)
	      f[j][i+j]=min(f[j][i+j],f[j][k]+f[k+1][i+j]);
}

int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1; i<=n; ++i)
	  for(int j=1; j<=m; ++j)
	    scanf("%d",&a[i][j]);
	init();
	for(int i=1; i<=m; ++i)
	{
		l=inf; r=0;
		dfs(1,i);
		if(l!=inf && r!=0)
		{
			for(int j=l; j<=r; ++j)
			  for(int k=j; k<=r; ++k)
			    f[j][k]=1;
		} 
	}
	if(check())
	{
		printf("0\n%d\n",ans);
		return 0; 
	}
	dp();
	printf("1\n%d\n",f[1][m]);
	return 0;
}



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值