【NOIP2010】引水入城
【问题描述】
在一个遥远的国度,一侧是风景秀美的湖泊,另一侧则是漫无边际的沙漠。该国的行政区划十分特殊,刚好构成一个N 行M 列的矩形,如上图所示,其中每个格子都代表一座城市,每座城市都有一个海拔高度。
为了使居民们都尽可能饮用到清澈的湖水,现在要在某些城市建造水利设施。水利设施有两种,分别为蓄水厂和输水站。蓄水厂的功能是利用水泵将湖泊中的水抽取到所在城市的蓄水池中。因此,只有与湖泊毗邻的第1 行的城市可以建造蓄水厂。而输水站的功能则是通过输水管线利用高度落差,将湖水从高处向低处输送。故一座城市能建造输水站的前提,是存在比它海拔更高且拥有公共边的相邻城市,已经建有水利设施。
由于第N 行的城市靠近沙漠,是该国的干旱区,所以要求其中的每座城市都建有水利设施。那么,这个要求能否满足呢?如果能,请计算最少建造几个蓄水厂;如果不能,求干旱区中不可能建有水利设施的城市数目。
N<= 500,M<= 500
【分析】
显然,我们可以简单的通过搜索判断是否有解。这里推荐bfs,如果用dfs有爆栈的风险。
然后对于第一行的每个点我们再通过搜索求出其所能到达最后一行所有的点,可以证明这些点一定是连续的。假设这些点中间如果有断开的话,设中间不能走到的那个点为x,那么x必大于左边的高度,必大于右边的高度,必大于上一行的高度,则x这个点无法到达,即此问无解,与之前我们确认过有解相矛盾,假设不成立,故命题成立。
这样我们算出第一行每个点所对应的最后一行的区间,记下左边界和右边界,然后就是典型的线段覆盖动规了。
f[i] = min(f[i],f[g[j].l - 1] + 1) (g[j].l <= i && g[j].r >= i)
g[j]表示每个点能够到达的区间。
做后直接输出f[m]就可以了
【代码】
#include <iostream> #include <cstring> #include <algorithm> #include <cstdio> using namespace std; struct apair { int l,r; }g[520]; struct node { int x,y; }q[300000]; int n,m,now; int a[520][520],f[520]; bool p[520][520]; const int xy[4][2] = {{0,1},{1,0},{0,-1},{-1,0}}; void bfs() { int h = 0,t = 0; for (int i = 1;i <= m;i ++) { p[1][i] = true; q[++t].x = 1; q[t].y = i; } while (h < t) { node u = q[++h]; for (int i = 0;i < 4;i ++) { node v; v.x = u.x + xy[i][0]; v.y = u.y + xy[i][1]; if (v.x > n || v.x < 1 || v.y > m || v.y < 1) continue; if (a[u.x][u.y] <= a[v.x][v.y]) continue; if (p[v.x][v.y]) continue; p[v.x][v.y] = true; q[++t] = v; } } } void dfs(int x,int y) { p[x][y] = true; if (x == n) { g[now].l = min(g[now].l,y); g[now].r = max(g[now].r,y); } for (int i = 0;i < 4;i ++) { int xx = x + xy[i][0]; int yy = y + xy[i][1]; if (xx > n || xx < 1 || yy > m || yy < 1) continue; if (a[x][y] <= a[xx][yy]) continue; if (!p[xx][yy]) dfs(xx,yy); } } 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]); bfs(); int rest = 0; for (int i = 1;i <= m;i ++) if (!p[n][i]) rest ++; if (rest) { cout << 0 <<endl; cout << rest <<endl; return 0; } cout << 1 << endl; for (int i = 1;i <= m;i ++) { memset(p,0,sizeof(p)); now = i; g[now].l = m + 1; g[now].r = 0; dfs(1,i); } f[0] = 0; for (int i = 1;i <= m;i ++) { f[i] = 1 << 30; for (int j = 1;j <= m;j ++) if (i >= g[j].l && i <= g[j].r) f[i] = min(f[i],f[g[j].l - 1] + 1); } cout << f[m]; }