[NOIP2010 提高组] 引水入城 题解

题目描述

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

为了使居民们都尽可能饮用到清澈的湖水,现在要在某些城市建造水利设施。水利设施有两种,分别为蓄水厂和输水站。蓄水厂的功能是利用水泵将湖泊中的水抽取到所在城市的蓄水池中。

因此,只有与湖泊毗邻的第 1 行的城市可以建造蓄水厂。而输水站的功能则是通过输水管线利用高度落差,将湖水从高处向低处输送。故一座城市能建造输水站的前提,是存在比它海拔更高且拥有公共边的相邻城市,已经建有水利设施。由于第 N 行的城市靠近沙漠,是该国的干旱区,所以要求其中的每座城市都建有水利设施。那么,这个要求能否满足呢?如果能,请计算最少建造几个蓄水厂;如果不能,求干旱区中不可能建有水利设施的城市数目。

输入格式

每行两个数,之间用一个空格隔开。输入的第一行是两个正整数 N,M,表示矩形的规模。接下来 N 行,每行 M 个正整数,依次代表每座城市的海拔高度。

输出格式

两行。如果能满足要求,输出的第一行是整数 1,第二行是一个整数,代表最少建造几个蓄水厂;如果不能满足要求,输出的第一行是整数 0,第二行是一个整数,代表有几座干旱区中的城市不可能建有水利设施。

输入输出样例

输入 #1

2 5
9 1 5 4 3
8 7 6 1 2

输出 #1

1
1

思路

前置知识: 搜索

对于水流的过程的搜索就不多阐述了

对于最后一行没有标记的格子直接统计并输出即可

对于有解(最后一行全部覆盖)的最少统计:

经过思考可以发现一个水流到最后一行的覆盖必是一个连续的线段

  • 我会骗分!

由于有部分测试点的n=1n=1,也即有部分城市既是沿水城市又是干旱城市,所以可以根据这个特性来骗分,期望得分00~1010分。

  • 我会搜索!

我们可以通过搜索来判断是否能够满足要求(若不能够满足要求则能够求出有几座干旱区中的城市不可能建有水利设施),因此可以得到部分的分数,期望得分0~30分。

  • 我会搜索+动态规划! 我们可以根据前面的一种方法来进行改进,我们可以用动态规划来求出能够满足时最少需要的蓄水厂的个数,我们可以在宽搜的时候求出每个点所能够去到的最左的点(左端点)和最右的点(右端点),然后用fz[i][j]来表示ii和jj之间最少需要建多少座蓄水厂,并且用d[i]来表示1n最少需要多少座蓄水厂(注意一开始要将这两个数组初始化为∞哦)。我们可以将可以到达其他出口且不被其他入口所到达的点的左端点至右端点的值改为11,例如左端点=2,右端点=7时将fz[2][2]fz[2][7]的值改为1。得到fz[]数组之后我们就可以求出d[]数组了,不难想出状态转移方程:

d[i]=min(d[i],d[j]+fz[j+1][i])d[i]=min(d[i],d[j]+fz[j+1][i])

由此得解,最后的答案即为d[m]。

下面上100分代码~

#include <cstdio>
#include <cstdlib>
#include <cstring>
int ma[501][501],fl[501][501],t[501][501],d[501];
struct nodea{ int v,l,r; } a[1000001];
struct nodeb{ int x,y; } f[1000001];
int dx[4]={1,0,-1,0};
int dy[4]={0,1,0,-1};
int fz[501][501];
int v[1000001];
int n=0,m=0;
int min(int x,int y)
{
    return x<y?x:y;
}
int max(int x,int y)
{
    return x>y?x:y;
}
void bfs(int sx,int sy)
{
    memset(fl,0,sizeof(fl));
    if(n==1)
    {
        v[sy]=1;
        a[sy].l=a[sy].r=sy;
    }
    int tou=1,wei=2;
    f[1].x=sx,f[1].y=sy;
    fl[sx][sy]=1;
    while(tou<wei)
    {
        for(int i=0;i<=3;i++)
        {
            int nx=f[tou].x+dx[i];
            int ny=f[tou].y+dy[i];
            if(ma[nx][ny]<ma[f[tou].x][f[tou].y] && fl[nx][ny]==0)
            {
                if(nx>=1 && nx<=n && ny>=1 && ny<=m)
                {
                    if(nx==1)
                    {
                        a[ny].v=0;
                    }
                    if(nx==n)
                    {
                        v[ny]=1;
                        a[sy].l=min(a[sy].l,ny);
                        a[sy].r=max(a[sy].r,ny);
                    }
                    f[wei].x=nx;
                    f[wei].y=ny;
                    fl[nx][ny]=1;
                    wei++;
                }
            }
        }
        tou++;
    }
}
void soua()
{
    for(int i=1;i<=m;i++)
    {
        a[i].l=999999999;
        a[i].r=0;
        a[i].v=1;
        v[i]=0;
    }
    for(int i=1;i<=m;i++)
    {
        if(a[i].v==1)
        {
            bfs(1,i);
        }
    }
}
void soub()
{
    int su=0;
    for(int i=1;i<=m;i++)
    {
        if(v[i]==0)
        {
            su++;
        }
    }
    if(su!=0)
    {
        printf("0\n%d",su);
        exit(0);
    }
}
void dp()
{
    for(int i=1;i<=m;i++)
    {
        d[i]=999999999;
        for(int j=1;j<=m;j++)
        {
            fz[i][j]=999999999;
        }
    }
    for(int t=1;t<=m;t++)
    {
        if(a[t].r!=0)
        {
            for(int i=a[t].l;i<=a[t].r;i++)
            {
                for(int j=i;j<=a[t].r;j++)
                {
                    fz[i][j]=1;
                    if(i==1)
                    {
                        d[j]=1;
                    }
                }
            }
        }
    }
    for(int i=1;i<=m;i++)
    {
        for(int j=1;j<=i-1;j++)
        {
            d[i]=min(d[i],d[j]+fz[j+1][i]);
        }
    }
    printf("1\n%d",d[m]);
}
int main()
{
    scanf("%d %d",&n,&m);
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=m;j++)
        {
            scanf("%d",&ma[i][j]);
        }
    }
    soua();
    soub();
    dp();
    return 0;
}

在百忙之中写一篇题解也比较辛苦,别忘了点个赞!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值