题解 引水入城

本文介绍了一种结合记忆化搜索与贪心策略解决特定覆盖问题的方法。通过记忆化搜索确定每个节点能够覆盖的底层区间,并利用贪心策略选择最少数量的区间以覆盖整个范围。代码实现使用C++完成。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

@luogu
@LOJ

从上向下记忆化搜索,保存每个点能覆盖的最底层的区间。如果一个点能到达的区间不连续,那么一定没有方案。然后做一个基础贪心:从n个区间中选出尽量少的区间,覆盖1-n

#include<cstdio>
#include<cstring>
#include<algorithm>
#define N 510
#define ll long long
#define max(x,y) ((x)>(y) ? (x) : (y))
#define min(x,y) ((x)<(y) ? (x) : (y))
using namespace std;

inline int getint() {
    int t=0,p;
    char ch=getchar();
    for(;ch!='-' && !(ch>='0' && ch<='9');ch=getchar());
    if(ch=='-') p=-1,ch=getchar();
    else p=1;
    for(;ch>='0' && ch<='9';ch=getchar()) {
        t=t*10+ch-48;
    }
    return t*p;
}

struct Line {
    int l,r;
    Line() {
        l=r=0;
    }
    bool operator < (const Line &o) const {
        return l<o.l;
    }
} b[N][N],c[N];

const int dx[4]={0,1,0,-1};
const int dy[4]={-1,0,1,0};
int a[N][N],n,m;
bool flag,f[N][N];

void Iscream(int x,int y) {   //记忆化搜索,b[i][j] 保存每个点能覆盖的底层区间。
    if(f[x][y]) return;
    f[x][y]=true;
    if(x==n) {b[x][y].l=y;b[x][y].r=y;}
    for(int i=0;i<4;i++) {
        int nx=x+dx[i];
        int ny=y+dy[i];
        if(nx>0 && nx<=n && ny>0 && ny<=m && a[nx][ny]<a[x][y]) {
            if(!f[nx][ny]) Iscream(nx,ny);
            if(b[nx][ny].l==0 && b[nx][ny].r==0) continue;
            if(b[x][y].l==0 || b[x][y].r==0) {
                b[x][y].l=b[nx][ny].l;
                b[x][y].r=b[nx][ny].r;
            } else {
                if(b[x][y].r<b[nx][ny].l-1 || b[x][y].l>b[nx][ny].r+1) {flag=true;}
                b[x][y].l=min(b[x][y].l,b[nx][ny].l);
                b[x][y].r=max(b[x][y].r,b[nx][ny].r);
            }
        }
    }
}

int main() {
//	freopen("flow.in","r",stdin);
//	freopen("flow.out","w",stdout);
    n=getint();m=getint();
    for(int i=1;i<=n;i++) {
        for(int j=1;j<=m;j++) {
            a[i][j]=getint();
        }
    }
    flag=false;
    memset(f,false,sizeof f);
    for(int i=1;i<=m;i++) {
        Iscream(1,i);
        c[i]=b[1][i];
//		printf("%d %d\n", b[1][i].l,b[1][i].r);
    }
    if(flag) {
        int ans=0;
        for(int i=1;i<=m;i++) {
            ans+=!f[n][i];
        }
        printf("0\n%d\n",ans);
        return 0;
    }
    int L=1;
    sort(c+1,c+m+1);
    int ans=0;
    int j;
    for(int i=1;i<=m;i=j) {  //贪心策略
        if(c[i].l>L) break;
        ans++;
        j=i+1;
        int mx=c[i].r;
        for(;j<=m && c[j].l<=L;j++) {
            if(c[j].r<L) continue;
            mx=max(mx,c[j].r);
        }
        if(mx>=L) L=mx+1;
        if(L>m) break;
    }
    if(L<=m) {
        int ans=0;
        for(int i=1;i<=m;i++) {
            ans+=!f[n][i];
        }
        printf("0\n%d\n",ans);
        return 0;
    }
    printf("1\n%d\n", ans);
    return 0;
}

### 地下城游戏的题解与算法分析 #### 题目概述 地下城游戏问题的核心在于骑士需要从左上角移动到右下角,过程中会遇到不同的房间。这些房间可能包含正数(增加健康点数)、负数(减少健康点数)或零(无影响)。目标是计算骑士在起点所需的最小初始健康点数,以确保能够成功到达终点[^4]。 #### 动态规划思路 动态规划是解决该问题的主要方法。由于骑士必须从终点倒推至起点,因此动态规划表 `dp[i][j]` 定义为从位置 `(i, j)` 到达终点所需的最小健康点数。通过逆向分析,可以避免复杂的前向状态转移逻辑[^2]。 #### 状态转移方程 状态转移方程如下: ```cpp dp[i][j] = max(min(dp[i+1][j], dp[i][j+1]) - dungeon[i][j], 1); ``` - `min(dp[i+1][j], dp[i][j+1])` 表示从当前格子出发时,选择向下或向右移动中所需最小健康点数较小的方向。 - 减去 `dungeon[i][j]` 是因为当前格子的影响需要被考虑。 - 使用 `max(..., 1)` 确保骑士在任何情况下健康点数都不低于 1[^3]。 #### 边界条件 为了简化边界处理,通常会在动态规划表的底部和右侧额外添加一行和一列,并初始化为一个足够大的值(如 `INF=0x3f3f3f3f`),以表示超出边界的情况。同时,将 `dp[m-1][n]` 和 `dp[m][n-1]` 初始化为 1,表示从终点出发时骑士至少需要 1 点健康点数[^3]。 #### 代码实现 以下是基于 C++ 的完整实现: ```cpp class Solution { public: int calculateMinimumHP(vector<vector<int>>& dungeon) { int m = dungeon.size(); int n = dungeon[0].size(); // 定义 INF 为足够大的值 int INF = 0x3f3f3f3f; // 创建动态规划表,多开一行和一列 vector<vector<int>> dp(m + 1, vector<int>(n + 1, INF)); // 初始化边界条件 dp[m][n - 1] = dp[m - 1][n] = 1; // 倒序填表 for (int i = m - 1; i >= 0; --i) { for (int j = n - 1; j >= 0; --j) { dp[i][j] = max(min(dp[i + 1][j], dp[i][j + 1]) - dungeon[i][j], 1); } } return dp[0][0]; } }; ``` #### 时间与空间复杂度 - **时间复杂度**:O(m * n),其中 m 和 n 分别为地下城网格的行数和列数。 - **空间复杂度**:O(m * n),用于存储动态规划表 `dp`。可以通过优化将空间复杂度降低至 O(n),但此处未展示优化版本。 #### 注意事项 1. 在进入负值的格子时,骑士的健康点数必须大于等于 1 才能继续前进。 2. 在进入正值的格子之前,骑士的健康点数也必须至少为 1,不能为负值。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值