「NOIP 2010」引水入城

Published on 2016-11-07

题目地址

描述

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

分析

首先用 BFS 来解决不能全部覆盖的情况。
现在必然存在一个选取方案,使得最后一行的城市被全部覆盖,我们考虑如何求解最少建造几个蓄水场。

定理一:从第一行的某个城市 uu 输水能够到达最后一个行的城市集合 SS,设最后一行的城市为 ,集合 SS 一定是一个区间 [L,R][L, R]
证明:不失一般性,我们假设集合 SS[l1,r1][l2,r2],r1<l2[l_1, r_1]\cup [l_2, r_2], r_1 < l_2,因为区间 [r1+1,l21][r_1 + 1, l_2 - 1] 一定能够被覆盖,若假设第一行的城市集合 AA 能覆盖 [r1+1,l21][r_1 + 1, l_2 - 1],第一行的 AA 到最后一行 [r1+1,l21][r_1 + 1, l_2 - 1] 的中的每条路径一定与城市 uu[l1,r1][l2,r2][l_1, r_1]\cup [l_2, r_2] 的路径有交点,而这就表明 uu 可以到达 [r1+1,l21][r_1 + 1, l_2 - 1],这就推出了矛盾。所以集合 SS 必定是一个区间 [L,R][L, R]

根据定理一,我们求出第一行的每个城市能覆盖的区间 [li,ri][l_i, r_i],现在问题变成了选择最少的区间,使得 [1,m][1, m] 全部被覆盖,这是经典的区间覆盖问题,排序后贪心可解决。
复杂度:O(nm2+mlogm)O(nm^2 + m\log m)

总结:本题需要得到第一行的城市覆盖最后一行的城市是一个区间,这样才能够转化为区间覆盖问题,否则就退化为集合覆盖问题,是没有多项式级别的解的。
吐槽:本题的精髓就在于证明定理一,如果不能证明它,那么这个题没有任何意义。所以我没有办法理解为什么有些人说:“我不会证明!”,这或许是对自己极不负责任的一种态度吧,做再多的题,如果不能抓住每个题的精髓,那么做的题又有什么意义?我替这些人感到悲哀。

代码

//  Created by Sengxian on 2016/11/7.
//  Copyright (c) 2016年 Sengxian. All rights reserved.
//  Vijos 1777 BFS + 区间覆盖
#include <bits/stdc++.h>
using namespace std;

typedef long long ll;
inline int ReadInt() {
    static int n, ch;
    n = 0, ch = getchar();
    while (!isdigit(ch)) ch = getchar();
    while (isdigit(ch)) n = n * 10 + ch - '0', ch = getchar();
    return n;
}

const int MAX_N = 500 + 3, MAX_M = 500 + 3;
int n, m, a[MAX_N][MAX_M];
bool vis[MAX_N][MAX_M];

struct Point {
    int x, y;
    Point(int x = 0, int y = 0): x(x), y(y) {}
};

const int dx[] = {0, 0, 1, -1};
const int dy[] = {1, -1, 0, 0};

Point q[MAX_N * MAX_M];
void judge() {
    int l = 0, r = 0;
    memset(vis, 0, sizeof vis);
    for (int i = 0; i < m; ++i) vis[0][i] = true, q[r++] = Point(0, i);
    while (r - l >= 1) {
        Point now = q[l++];
        for (int k = 0; k < 4; ++k) {
            Point ne = Point(now.x + dx[k], now.y + dy[k]);
            if (ne.x >= 0 && ne.x < n && ne.y >= 0 && ne.y < m && a[now.x][now.y] > a[ne.x][ne.y] && !vis[ne.x][ne.y]) {
                vis[ne.x][ne.y] = true;
                q[r++] = ne;            
            }
        }
    }
    int cnt = 0;
    for (int i = 0; i < m; ++i) cnt += vis[n - 1][i];
    if (cnt != m) {
        printf("0\n%d\n", m - cnt);
        exit(0);
    }
}

typedef pair<int, int> range;

range Bfs(int sx, int sy) {
    int l = 0, r = 0;
    memset(vis, 0, sizeof vis);
    q[r++] = Point(sx, sy), vis[sx][sy] = true;
    while (r - l >= 1) {
        Point now = q[l++];
        for (int k = 0; k < 4; ++k) {
            Point ne = Point(now.x + dx[k], now.y + dy[k]);
            if (ne.x >= 0 && ne.x < n && ne.y >= 0 && ne.y < m && a[now.x][now.y] > a[ne.x][ne.y] && !vis[ne.x][ne.y]) {
                vis[ne.x][ne.y] = true;
                q[r++] = ne;            
            }
        }
    }
    int ll = m, rr = m;
    for (int i = m - 1; i >= 0; --i)
        if (vis[n - 1][i])
            ll = i;
    for (int i = 0; i < m; ++i)
        if (vis[n - 1][i])
            rr = i;
    return range(ll, rr + 1);
}

range ranges[MAX_M];

void solve() {
    for (int i = 0; i < m; ++i)
        ranges[i] = Bfs(0, i);
    sort(ranges, ranges + m);
    int i = 0, pos = 0, cnt = 0;
    while (pos < m) {
        int mx = -1;
        while (i < m && ranges[i].first <= pos) mx = max(mx, ranges[i++].second);
        assert(mx != -1);
        pos = mx, cnt++;
    }
    printf("1\n%d\n", cnt);
}

int main() {
    n = ReadInt(), m = ReadInt();
    for (int i = 0; i < n; ++i)
        for (int j = 0; j < m; ++j)
            a[i][j] = ReadInt();
    judge();
    solve();
    return 0;
}