[NOIP2010 提高组] 引水入城

文章讲述了NOIP2010提高组竞赛中的一道图论问题——引水入城。题目要求判断从第一行的任意一点出发能否到达最后一行的所有点,并求出最少需要多少个出发点。解题思路基于动态规划,通过DFS搜索确定每个点能到达的最远位置,并找到覆盖所有最后一行点的最小区间。最后给出了C++实现的代码示例。

题目链接:P1514 [NOIP2010 提高组] 引水入城 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

题意:

路只能严格由高到低,问从第一行的任意点出发,是否能经过最后一行的所有点?如果不行,有几个点没法到达;如果可以,最少需要多少个点作为出发点。

思路:

假设可以完成,那么对于一个点出发到最后一行能到达的位置一定是一段连续的区间。

假设存在点1到达最后一行的点不是连续,那么中间的部分必然有其他的起点来连接,则连接的过程和点1到最后一行的路径必然有重叠的点,说明该重叠的点可以通往中间部分,说明假设有误。

对于最小的区间查找,首先要以左边为基点,找到一个区间能包含左边,同时靠右端最近,然后更新左端点为该区间的最右处+1;再遍历重复这个过程。

代码:

#include <cstdio>
#include <cmath>
#include <cstring>
#include <string>
#include <queue> 
#include <iostream>
#include <algorithm>
#define ll long long
#define pi 3.1415926
using namespace std;

const int MAXN=1e6;
const int mod=6e5;
const int N=505;
int X1,Y1,X2,Y2;
struct node{
    double x,y;
}s[N];
void test(int x){
    cout<<x<<endl;
}
int dx[4]={1,0,-1,0};
int dy[4]={0,1,0,-1};

int l[N][N],r[N][N];//代表该点最远能到达的左和右
int road[N][N];
int vis[N][N]={0};
int n,m;

void dfs(int x,int y){
    vis[x][y]=1;
    for(int i=0;i<4;i++){
        int tx=x+dx[i];
        int ty=y+dy[i];
        
        //是否走过,走过不用再搜,但是需要更新区间
        //如果不符合规范的点,也不必继承下面的更新
        if(tx>=1&&tx<=n&&ty>=1&&ty<=m&&road[x][y]>road[tx][ty]){
            if(vis[tx][ty]==0) dfs(tx,ty);
            l[x][y]=min(l[x][y],l[tx][ty]);
            r[x][y]=max(r[x][y],r[tx][ty]);
        }    
    }
}

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);cout.tie(0);
    memset(l,0x3f3f3f,sizeof(l));//因为查找左区间的时候是尽量小,初始化就是尽量大;右区间最小为0不用再操作了
    
    cin>>n>>m;
    for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++){
            cin>>road[i][j];
        }
    }
    for(int i=1;i<=m;i++){
        l[n][i]=r[n][i]=i;//初始化最后一行的每一个点能区间为自己本身
    }
    for(int i=1;i<=m;i++){
        if(vis[1][i]!=1) dfs(1,i);
    }
    int count=0;
    bool flag=false;
    for(int i=1;i<=m;i++){
        if(vis[n][i]==0){
            flag=true;
            count++;
        }
    }
    int ans=0;
    if(flag){
        cout<<0<<endl<<count<<endl;
        return 0;
    }
    else{
        
        int left=1;
        //test(left);
        int right=r[1][1];
        while(right!=m){
            for(int i=1;i<=m;i++){
                if(l[1][i]<=left){
                    right=max(right,r[1][i]);
                }
            }
            left=right+1;
            ans++;
        }
    }
    cout<<1<<endl<<ans<<endl;
    return 0;
}
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

爱吃芒果的蘑菇

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值