P1514 [NOIP2010 提高组] DP + 贪心

158 篇文章 1 订阅
题意

传送门 P1514 [NOIP2010 提高组] 引水入城

题解

容易通过 D F S DFS DFS 求解在城市 ( 0 , j ) (0,j) (0,j) 处建蓄水厂的情况下,在 N − 1 N-1 N1 行可以建输水站的城市集合 S ( 0 , j ) S_{(0,j)} S(0,j)。由于连续的水利设施需要满足高度的单调性,搜索的路径上不可能出现环,那么可以记忆化搜索。 d p [ i ] [ j ] dp[i][j] dp[i][j] 代表 S ( i , j ) S_{(i,j)} S(i,j),对集合进行二进制状态压缩,那么有递推式
d p [ i ] [ j ] = 2 j [ i = N − 1 ] ⋃ d p [ k ] [ l ] , H [ i ] [ j ] > H [ k ] [ l ] , ( i , j ) 与 ( k , l ) 有 公 共 边 dp[i][j]=2^{j[i=N-1]}\bigcup dp[k][l],H[i][j]>H[k][l],(i,j)与(k,l)有公共边 dp[i][j]=2j[i=N1]dp[k][l],H[i][j]>H[k][l],(i,j)(k,l) 问题转化为从行 0 0 0 中选取一个模值最小的城市集合 A A A,满足在集合对应的城市建蓄水厂的情况下,行 N − 1 N-1 N1 的城市都可以建输水站,即
⋃ d p [ 0 ] [ j ] = 2 M − 1 , ( 0 , j ) ∈ A \bigcup dp[0][j]=2^M-1,(0,j)\in A dp[0][j]=2M1,(0,j)A 这类从决策集合中选取一个子集,在满足某些约束的条件下的最优化问题,形式与背包问题一致,可以考虑依次处理行 0 0 0 的每一座城市,以约束条件为状态进行转移。但问题在于约束条件的值域为 O ( 2 M ) O(2^M) O(2M),且每轮决策后最坏情况下状态空间翻倍,那么也无法对状态哈希并使用滚动数组。

容易观察到,若某个集合 S ( 0 , j ) S_{(0,j)} S(0,j) 在位置上不连续,考虑输水需要满足的单调性,那么被集合城市包围的城市不可能建输水站。则在满足行 N − 1 N-1 N1 都可以建水利设施的情况下,集合 S ( 0 , j ) S_{(0,j)} S(0,j) 在位置上连续,那么可以简单地用连续区间的左右端点表示集合。

问题化简为已知城市 ( 0 , j ) (0,j) (0,j) 处建蓄水厂的情况下,在 N − 1 N-1 N1 行可以建输水站的城市区间 [ l j , r j ) [l_j,r_j) [lj,rj),求可以覆盖行 N − 1 N-1 N1 区间 [ 0 , M ) [0,M) [0,M) 的最小区间数量。这个问题不需要使用背包 D P DP DP 求解,而可以更简单地使用贪心策略,即从左端点 0 0 0 开始,依次选择左端点被当前覆盖区间包含或位于覆盖区间右侧相邻位置,且右端点值最大的区间。总时间复杂度 O ( N M ) O(NM) O(NM)

#include <bits/stdc++.h>
using namespace std;
const int maxn = 505, inf = 0x3f3f3f3f;
const int dx[4] = {0, 0, -1, 1}, dy[4] = {-1, 1, 0, 0};
struct node
{
    int l, r;
    node() : l(inf), r(-inf) {}
    bool operator<(const node &o) const { return l < o.l; }
} mem[maxn][maxn], A[maxn];
int N, M, H[maxn][maxn];
bool used[maxn][maxn];

void rec(int x, int y)
{
    if (used[x][y])
        return;
    used[x][y] = 1;
    auto &res = mem[x][y];
    for (int i = 0; i < 4; ++i)
    {
        int nx = x + dx[i], ny = y + dy[i];
        if (0 <= nx && nx < N && 0 <= ny && ny < M && H[x][y] > H[nx][ny])
        {
            rec(nx, ny);
            auto t = mem[nx][ny];
            res.l = min(res.l, t.l), res.r = max(res.r, t.r);
        }
    }
    if (x == N - 1)
        res.l = min(res.l, y), res.r = max(res.r, y + 1);
}

int main()
{
    cin >> N >> M;
    for (int i = 0; i < N; ++i)
        for (int j = 0; j < M; ++j)
            cin >> H[i][j];
    for (int i = 0; i < M; ++i)
        rec(0, i), A[i] = mem[0][i];
    int omit = 0;
    for (int i = 0; i < M; ++i)
        omit += mem[N - 1][i].l == inf;
    if (omit > 0)
    {
        cout << 0 << '\n'
             << omit << '\n';
        return 0;
    }
    sort(A, A + M);
    int cur = 0, nxt = 0, res = 0;
    for (int i = 0; i < M; ++i)
    {
        if (A[i].l == inf)
            break;
        nxt = max(nxt, A[i].r);
        if ((i != M - 1 && A[i + 1].l > cur) || (i == M - 1 && cur < M))
            ++res, cur = nxt;
    }
    cout << 1 << '\n'
         << res << '\n';
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值