P1514 [NOIP2010 提高组] 引水入城-DFS+记忆化搜索

题目+提交地址

题目意思就是,每个点自身都有一个权重,在第一行选中x个点(这些点可以扩散到比它本身权重更低的点),问能否将最后一行的点都覆盖完。
若能,求出最小的x;
若不能,求出最后一行不能被覆盖的点的数量
题目意思
题目给的样例也很容易明白,我的第一思路是先对最后一行进行预处理,将最后一行的每个点存到结构体中,然后从权重最小的点进行左右搜索,扩散到比它大的点,看这个样例:
在这里插入图片描述
那么经过预处理后,只需要从3,2,2进行搜索就行了,然后这些点往上搜索,找权重比它们大的点进行dfs找到第一行的蓄水池,然后对第一行找到的点dfs搜索比它权重小的点。但是后来发现这种思路是行不通的,因为dfs有多个方向,无法确定需要建立几个蓄水池。

然后突然想到,可以从上往下搜索,而且是从最高的点进行搜索(这种贪心是错误的),这样只需要判断最后一行哪些点没有被覆盖就可以了
码完交上去是30分

#include <bits/stdc++.h>
using namespace std;
#define de(x) cout<<x<<" ";
#define Pu puts("");
#define sf(x) scanf("%d",&x);
typedef long long ll;
const int N=5e2+10,mod=100003;
const int inf=0x3f3f3f3f;
struct E{
    int u,id;
};
struct cmp{//对于所有的城市能建立蓄水池的城市中,从海拔最高的开始dfs,这是最优的方法(贪心)
    bool operator()(E a,E b){
        return a.u<b.u;
    }
};
priority_queue<E,vector<E>,cmp>q;

int n,m,ans;
int sz=0;//干旱区已经建立输水站的城市
bool st[N][N];
int u[N][N];

int dx[]={0,0,1,-1},dy[]={1,-1,0,0};
void dfs(int x,int y){
    if(sz==m) return ;//干旱区都已经建立了输水站
    int l,r;
    for(int i=0;i<4;i++){
        l=x+dx[i];r=y+dy[i];
        if(l<1||r<1||l>n||r>m) continue;
        if(st[l][r]==1) continue;
        if(u[l][r]>=u[x][y]) continue;

        st[l][r]=1;
        if(l==n) sz++;
        dfs(l,r);
    }
}
int main(){
    cin>>n>>m;
    for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++){
            sf(u[i][j])
            if(i==1) q.push(E{u[i][j],j});
        }
    }
    E nw,nx;
    int id,u;
    while(!q.empty()){
        nw=q.top();q.pop();
        id=nw.id;u=nw.u;
        if(st[1][id]==1) continue;//这个位于第一行的城市已经建立了输水站
        st[1][id]=1;
        ans++;//建立的蓄水池加一
        dfs(1,id);

        if(sz==m) break;//所有干旱区里的城市均已经建立了输水站
    }
    if(sz==m){
        printf("1\n%d\n",ans);
    }else{
        printf("0\n");//接下来查询还没有建立输水站的干旱城市
        int res=0;
        for(int i=1;i<=m;i++){
            if(st[n][i]==0) res++;
        }
        printf("%d\n",res);
    }
    return 0;
}

看了看题解发现,刚才的贪心思想是错误的,因为有可能在中间的过程中,权重突然变得很低了……

通过看题解知道,每个点覆盖的一定是连续的线段!!
洛谷题解区有个图讲的很清楚,可以去看一下
在这里插入图片描述
于是,我又继续码
这种写法是很暴力的一种方法,没有使用记忆化搜索
AC代码

#include <bits/stdc++.h>
using namespace std;
#define de(x) cout<<x<<" ";
#define Pu puts("");
#define sf(x) scanf("%d",&x);
typedef long long ll;
const int N=5e2+10,mod=100003;
const int inf=0x3f3f3f3f;
int n,m,ans;
struct E{
    int l=inf,r=0;
}tail[N];
int u[N][N];//高度
int wei[N];//判断最后一行的某个点是否可以被覆盖

bool st[N][N];//dfs时的标记数组
int dx[]={0,0,1,-1},dy[]={1,-1,0,0};
bool cmp(E a,E b){
    if(a.l==b.l) return a.r>b.r;
    return a.l<b.l;
}
void dfs(int x,int y,int id){
    st[x][y]=1;
    if(x==n){//防止只有一行的情况
        wei[y]=1;
        tail[id].l=min(tail[id].l,y);
        tail[id].r=max(tail[id].r,y);
    }

    int l,r;
    for(int i=0;i<4;i++){
        l=x+dx[i];r=y+dy[i];
        if(l<1||r<1||l>n||r>m) continue;
        if(st[l][r]==1) continue;
        if(u[l][r]>=u[x][y]) continue;
        dfs(l,r,id);
    }
}
int main(){
    cin>>n>>m;
    for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++){
            sf(u[i][j])
        }
    }
    for(int i=1;i<=m;i++){
        dfs(1,i,i);
        memset(st,0,sizeof(st));
    }
    int sz=0;
    for(int i=1;i<=m;i++){
        if(wei[i]==0) sz++;
    }
    if(sz){
        printf("0\n%d\n",sz);//接下来查询还没有建立输水站的干旱城市
    }else{
        //我的写法:不行
        // sort(tail+1,tail+m+1,cmp);
        // int last=tail[1].r;ans++;
        // int id=2;//有一种可能是所有的区间左端点都为1
        // while(id<=m){
        //     if(tail[1].r==m) break;//特殊情况:第一个点可以覆盖完

        //     if(tail[id].l==inf) break;
        //     int tmp_r=0;//但是也有可能从第二个区间之后他们左端点都一样
        //     while(tail[id].l<=last&&id<=m){
        //         tmp_r=max(tmp_r,tail[id].r);
        //         id++;
        //     }
        //     ans++;
        //     last=tmp_r;

        //     if(tmp_r==m) break;
        // }
        // printf("1\n%d\n",ans);

        //写法一:
        // int f[N];memset(f,0x3f,sizeof(f));f[0]=0;
        // for(int i=1;i<=m;i++){
        //     for(int j=1;j<=m;j++){
        //         if(tail[j].l<=i&&tail[j].r>=i) f[i]=min(f[i],f[tail[j].l-1]+1);
        //     }
        // }
        // printf("1\n%d\n",f[m]);

        //写法二:
        int left=1;
        while(left<=m){
            int max_r=0;
            for(int i=1;i<=m;i++){
                if(tail[i].l<=left){
                    max_r=max(max_r,tail[i].r);
                }
            }
            ans++;
            left=max_r+1;
        }
        printf("1\n%d\n",ans);
    }
    return 0;
}

记忆化搜索:

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值