【dp】NOIP2010提高组引水入城

【问题描述】

在一个遥远的国度,一侧是风景秀美的湖泊,另一侧则是漫无边际的沙漠。该国的行政区划十分特殊,刚好构成一个N行M列的矩形,其中每个格子都代表一座城市,每座城市都有一个海拔高度。

为了使居民们都尽可能饮用到清澈的湖水,现在要在某些城市建造水利设施。水利设施有两种,分别为蓄水厂和输水站。蓄水厂的功能是利用水泵将湖泊中的水抽取到所在城市的蓄水池中。因此,只有与湖泊毗邻的第1行的城市可以建造蓄水厂。而输水站的功能则是通过输水管线利用高度落差,将湖水从高处向低处输送。故一座城市能建造输水站的前提,是存在比它海拔更高且拥有公共边的相邻城市,已经建有水利设施。

由于第N行的城市靠近沙漠,是该国的干旱区,所以要求其中的每座城市都建有水利设施。那么,这个要求能否满足呢?如果能,请计算最少建造几个蓄水厂;如果不能,求干旱区中不可能建有水利设施的城市数目。

【题目分析】
首先这道题确实不太好像,第一次写的时候打的暴力,过了几个点,后来仔细想一想,发现一个重要的性质:
在任意一处建造蓄水站之后对于最下方所能达到的区域必是一个连续的区域,若不连续则必为无解情况。

第一列第二列第三列第四列第五列
14541
231000032
11000010000100001
0100000100000

我们假设有一座“大山”,将其拦住,那么就比如说上图,第三个蓄水站浇筑的区间是不连续的,那么很明显这是一个无解的情况。

完成这个dp需要求出所有蓄水站能到达底层范围的一个区间,但是时间限制不允许我们暴力广搜,所以加入一个小优化,对于已经拓展过的点就没有必要再拓展一遍了,因为即使该点枚举到了蓄水站位置,那么也不会比之前到过的情况更优,所以没有必要再来一遍。

所以简单地说可以先进行搜索,判断是否有解(dfs容易爆栈,不推荐)。在有解的前提下,每个蓄水站浇灌的区间一定是连续的,那么求出每个蓄水站的左区间和右区间,dp一次就可以解决问题。
dp状态转移方程:F[i]=min(F[i],F[LEFT[j]-1]+1)

再来分析一下方程式
for(i=2;i<=m;i++)
for(j=1;j<=m;j++)
{
if(LEFT[j]<=i&&RIGHT[j]>=i)
F[i]=min(F[i],F[LEFT[j]-1]+1);
else if(LEFT[j]>i)
break;
}
其中F[i]数组存储的是底层前i个行政区都有水的情况下所需要的最少蓄水站数量。所以枚举寻找一个区间使得它能包含住第i个点,由此++而来。那为什么要break呢?因为如果左侧的蓄水站都不能达到该区域,那么右侧的区域是一定到达不了的,就没有必要考虑后面的蓄水站对当前情况的影响了。

最后借助此题说一下本蒟蒻的代码
很有争议的一种写法,把所有内容写到函数里面,个人觉得会思路很清晰,也比较好调试。

#include <iostream>
#include <cstdio>
#include <queue>
#include <climits>
using namespace std;
const int L=501;
const int INF=INT_MAX;
int n,m;  int map[L][L];  int F[L],LEFT[L],RIGHT[L];//main
struct zk { int x,y; };  queue<zk> q;  bool used[L][L];//bfs
bool LEFTused[L][L],RIGHTused[L][L];//dfs
void read()
{
    int i,j;
    scanf("%d%d",&n,&m);
    for (i=1;i<=m;i++)
        F[i]=INF;
    F[1]=1;
    for (i=1;i<=n;i++)
        for (j=1;j<=m;j++)
            scanf("%d",&map[i][j]);
    return;
}
bool bfs()
{
    while(!q.empty())  q.pop();
    zk temp;  temp.y=1; int a,b,i;
    for (i=1;i<=m;i++)
    {
        temp.x=i;
        q.push(temp);
        used[1][i]=1;
    }
    while(!q.empty())
    {
        temp=q.front();
        a=temp.y; b=temp.x;
        if (a>1&&!used[a-1][b]&&map[a][b]>map[a-1][b])
        {
            temp.y=a-1;  temp.x=b;
            q.push(temp);  used[a-1][b]=1;
        }
        if (a<n&&!used[a+1][b]&&map[a][b]>map[a+1][b])
        {
            temp.y=a+1;  temp.x=b;
            q.push(temp);  used[a+1][b]=1;
        }
        if (b>1&&!used[a][b-1]&&map[a][b]>map[a][b-1])
        {
            temp.y=a;  temp.x=b-1;
            q.push(temp);  used[a][b-1]=1;
        }
        if (b<m&&!used[a][b+1]&&map[a][b]>map[a][b+1])
        {
            temp.y=a;  temp.x=b+1;
            q.push(temp);  used[a][b+1]=1;
        }
        q.pop();
    }
    for (i=1;i<=m;i++)
        if (!used[n][i])
        return 0;
    return 1;
}
void dfs1(int num,int x,int y,int front)
{
    if(LEFTused[x][y]==1) return;
        LEFTused[x][y]=1;
    if(x==1) LEFT[y]=num;
    if(x-1>=1&&front!=2&&map[x-1][y]>map[x][y])  dfs1(num,x-1,y,1);
    if(x+1<=n&&front!=1&&map[x+1][y]>map[x][y])  dfs1(num,x+1,y,2);
    if(y-1>=1&&front!=4&&map[x][y-1]>map[x][y])  dfs1(num,x,y-1,3);
    if(y+1<=m&&front!=3&&map[x][y+1]>map[x][y])  dfs1(num,x,y+1,4);
    return;
}
void dfs2(int num,int x,int y,int front)
{
    if(RIGHTused[x][y]==1) return;
        RIGHTused[x][y]=1;
    if(x==1) RIGHT[y]=num;
    if(x-1>=1&&front!=2&&map[x-1][y]>map[x][y])  dfs2(num,x-1,y,1);
    if(x+1<=n&&front!=1&&map[x+1][y]>map[x][y])  dfs2(num,x+1,y,2);
    if(y-1>=1&&front!=4&&map[x][y-1]>map[x][y])  dfs2(num,x,y-1,3);
    if(y+1<=m&&front!=3&&map[x][y+1]>map[x][y])  dfs2(num,x,y+1,4);
    return;
}
void dp()
{
    int i,j;
    for(i=2;i<=m;i++)
        for(j=1;j<=m;j++)
        {
            if(LEFT[j]<=i&&RIGHT[j]>=i)
                F[i]=min(F[i],F[LEFT[j]-1]+1);
            else if(LEFT[j]>i)
                break;
        }
    return;
}
void work()
{
    int i,ans=0;
    if (!bfs())
    {
        for (i=1;i<=m;i++)
            if (!used[n][i])
            ans++;
        printf("0\n%d",ans);
    }
    else
    {
       for(i=1;i<=m;i++)
        dfs1(i,n,i,0);
       for(i=m;i>=1;i--)
        dfs2(i,n,i,0);
        dp();
        printf("1\n%d",F[m]);
    }
    return;

}
int main()
{
    freopen("flow.in","r",stdin);
    freopen("flow.out","w",stdout);
    read();
    work();
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值