[Wikioi 1066][NOIP 2010提高组]引水入城

来源:互联网 发布:上海品牌知恩女装 编辑:程序博客网 时间:2024/04/27 18:49

 

在一个遥远的国度,一侧是风景秀美的湖泊,另一侧则是漫无边际的沙漠。该国的行政 区划十分特殊,刚好构成一个N行M列的矩形,如上图所示,其中每个格子都代表一座城 市,每座城市都有一个海拔高度。 为了使居民们都尽可能饮用到清澈的湖水,现在要在某些城市建造水利设施。水利设施 有两种,分别为蓄水厂和输水站。蓄水厂的功能是利用水泵将湖泊中的水抽取到所在城市的 蓄水池中。因此,只有与湖泊毗邻的第1行的城市可以建造蓄水厂。而输水站的功能则是通 过输水管线利用高度落差,将湖水从高处向低处输送。故一座城市能建造输水站的前提,是 存在比它海拔更高且拥有公共边的相邻城市,已经建有水利设施。 由于第N行的城市靠近沙漠,是该国的干旱区,所以要求其中的每座城市都建有水利 设施。那么,这个要求能否满足呢?如果能,请计算最少建造几个蓄水厂;如果不能,求干 旱区中不可能建有水利设施的城市数目。

输入的每行中两个数之间用一个空格隔开。 输入的第一行是两个正整数N和M,表示矩形的规模。 接下来N行,每行M个正整数,依次代表每座城市的海拔高度。

输出有两行。如果能满足要求,输出的第一行是整数1,第二行是一个整数,代表最少 建造几个蓄水厂;如果不能满足要求,输出的第一行是整数0,第二行是一个整数,代表有 几座干旱区中的城市不可能建有水利设施。

2 5

9 1 5 4 3

8 7 6 1 2

1

1

【数据范围】 本题共有10个测试数据,每个数据的范围如下表所示: 测试数据编号 能否满足要求 N M 1 不能 ≤ 10 ≤ 10 2 不能 ≤ 100 ≤ 100 3 不能 ≤ 500 ≤ 500 4 能 = 1 ≤ 10 5 能 ≤ 10 ≤ 10 6 能 ≤ 100 ≤ 20 7 能 ≤ 100 ≤ 50 8 能 ≤ 100 ≤ 100 9 能 ≤ 200 ≤ 200 10 能 ≤ 500 ≤ 500 对于所有的10个数据,每座城市的海拔高度都不超过10^6

 

样例2 说明

 

数据范围

题目思路

这个题比较难,我花了一个下午才把它AC,但是题目从标题就给了暗示——水,显然此题需要用灌水法Floodfill来做,这道题是我第一次使用灌水法,何为灌水法?看下面这张图


设置一个二维数组plat,初始时清零,从第一行沿海地带开始,向四周扩散,只要比前面一个点高度低且不为1(避免重复劳动)的点,就将它置为1,灌水完成后,我们会发现置为1的路径上经过的是一个严格下降子序列。

下面开始解这个题,第一问很好做,从第一排所有的点开始把水灌满整个棋盘,再循环寻找最下面一排干旱区是否有没被水灌过的点,即可判断此题是否无解。

第二问要复杂多了,先从第一排第一个点开始,一个点一个点地灌水,寻找最后一排干旱区被水灌过的区间(这个区间一定是连续的,下面再证),记录在结构体中,将数组plat清零,再往第一排右边的点重复这样做。

这样我们能得到第一排每个点灌水后,干旱区的连续区间,这时问题转化为线段覆盖了,用贪心算法求出区间[1,m]完全覆盖所需线段数(我的代码中的贪心部分是使用别人的,自己的贪心还没过),问题解决。

下面证明为什么被水灌过的区间是连续的,使用反证法:如下图所示,如果这个区间中有一个点高度为+∞,那么水就无法从其他任何地方到这来,这个区间就不是连续的,而已经求出此数据有解(最后一排干旱区都被水灌过),矛盾,因此被水灌过的区间是连续的。


下面是代码:

#include <stdio.h>#include <stdlib.h>#include <string.h>#define MAXN 600#define MAXINT 10000000int map[MAXN][MAXN]; //map[行数][列数],标记每个点的高度int plat[MAXN][MAXN]; //plat[行数][列数],标记每个点能否被灌水int n,m; //n行 m列struct point{int l; //覆盖区间左端点列数int r; //覆盖区间右端点列数}station[MAXN];int max(int a,int b){if(a>b)return a;return b;}int cmp(const void *a,const void *b){struct point *c=(point *)a;struct point *d=(point *)b;if(c->l!=d->l) //左端点升序排return c->l-d->l;else //左端点相同。右端点降序排列return d->r-c->r;}void floodfill(int x,int y) //floodfill(点的行数,点的列数) 递归灌水法{if(x<1||y<1||x>n||y>m)return; //越出棋盘界限,退出plat[x][y]=1;//该点标记为1if(map[x+1][y]<map[x][y]&&plat[x+1][y]==0) //如果该点正下方的点比该点高度小,且没被灌过水(避免重复递归),向它灌水,(下面同此)floodfill(x+1,y);if(map[x-1][y]<map[x][y]&&plat[x-1][y]==0) //如果该点正下方的点比该点高度小,且没被灌过水(避免重复递归),向它灌水,(下面同此)floodfill(x-1,y);if(map[x][y+1]<map[x][y]&&plat[x][y+1]==0) //如果该点正下方的点比该点高度小,且没被灌过水(避免重复递归),向它灌水,(下面同此)floodfill(x,y+1);if(map[x][y-1]<map[x][y]&&plat[x][y-1]==0) //如果该点正下方的点比该点高度小,且没被灌过水(避免重复递归),向它灌水,(下面同此)floodfill(x,y-1);return;}int main(){int i,j,sum=0,dp[MAXN];memset(map,0,sizeof(map));memset(plat,0,sizeof(plat));scanf("%d%d",&n,&m);for(i=1;i<=n;i++){for(j=1;j<=m;j++)scanf("%d",&map[i][j]);}for(i=1;i<=m;i++)floodfill(1,i); //首先为了确定是否有解,从第一排所有点开始,将整个棋盘plat灌水for(i=1;i<=m;i++)if(plat[n][i]==0)sum++; //灌完水发现最后一排有没水的,把没水的点数一遍if(sum!=0) //有没水的点{printf("0\n%d\n",sum); //输出无解return 0;}for(i=1;i<=m;i++) //有解,则从第一排第一个点开始,每个点单独灌水,寻找最后一排有水的覆盖区间,灌完水清零{memset(plat,0,sizeof(plat));floodfill(1,i);for(j=1;j<=m;j++)if(plat[n][j]) //找到覆盖区间左端点{station[i].l=j,station[i].r=m;for(int k=j+1;k<=m;k++)if(!plat[n][k]){station[i].r=k-1;break;}break;}}sum=1;qsort(station,m+1,sizeof(station[0]),cmp);point goal=station[1];for(i=2;i<=m;i++){if(station[i].r>goal.r){goal.r=station[i].r;}if(station[i].l<=goal.r) //有重复{sum++;goal=station[i];}}printf("1\n%d\n",sum);return 0;}





0 0
原创粉丝点击