欢迎使用CSDN-markdown编辑器

2 篇文章 0 订阅
1 篇文章 0 订阅

NOIP2010 引水入城题解

题目描述

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

这里写图片描述

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

输入输出格式

输入格式:

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

输出格式:

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

【数据范围】

这里写图片描述

解题思路

思路一:搜索所有的选择,取最小的一种。复杂度为O(m!)

期望得分:30分。

思路二:考虑到如果存在可行解,则一个蓄水池所能到达的干旱区是连续的

证明:如果不连续,则中间不连续的几个区域左右一定都无法到达,即左右高度都要大于中间区域,而如果左右都能由同一个蓄水池到达,则上方一定是封闭的。如果上方也无法到达,说明没有蓄水池可以到达中间区域。

所以我们只需要对于每一个能建造蓄水池的地点跑一遍BFS,求出他所能到达的那一段连续干旱区,再跑线段覆盖的贪心即可(按照左端点排序,对于当前区域如果下一区域的左端点小于当前最右边端点就看当前和下一个区域的右端点谁大,否则直接选择当前节点)。复杂度O(n*m*m)

期望得分:80分(500*500*500)会超时

思路三:在思路二的基础上进行减枝。

注意到如果一个可建造蓄水池的区域可以到达另一个,则只需要任选一个即可,所以只在a[1][i]>=a[1][i-1]&&a[1][i]>=a[1][i+1]时才有必要跑BFS。

期望得分:100分
除此之外,还有一些小剪枝。
1.手写min,max函数
2.加inline
3.bfs时判重函数可用int类型,不必每次初始化
4.读入优化

参考程序

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
int n,m;
inline int read()
{
    int x=0;
    char ch;
    ch=getchar();
    while(ch<'0'||ch>'9') ch=getchar();
    while(ch>='0'&&ch<='9')
    {
        x=x*10+ch-'0';
        ch=getchar();
    }
    return x;
}
inline int mini(int x,int y)
{
    return x<y?x:y;
}
int a[505][505],ans;
bool water[505];
int vis[505][505];
bool vis1[505];
struct node
{
    int x,y;
}q[10000005];
struct line
{
    int l,r;
}f[505];
int bx[4]={-1,1,0,0};
int by[4]={0,0,1,-1};
bool cmp(line x,line y)
{
    if(x.l<y.l) return 1;
    else if(x.l==y.l&&x.r<y.r) return 1;
    return 0;
}
inline void flow(int sy)
{
    memset(water,0,sizeof(water));
    int head=0,tail=1,x,y;
    q[1].x=1;
    q[1].y=sy;
    f[sy].l=m+1;
    if(q[1].x==n) 
    {   
        water[sy]=true;
        vis[1][sy]=sy;
        vis1[sy]=true;
        f[sy].l=mini(q[1].y,f[sy].l);
    }
    while(head<tail)
    {
        head++;
        for(int i=0;i<4;i++)
        {
            x=q[head].x+bx[i];
            y=q[head].y+by[i];
            if(x>0&&x<=n&&y>0&&y<=m&&a[x][y]<a[q[head].x][q[head].y]&&vis[x][y]!=sy)
            {
                tail++;
                q[tail].x=x;
                q[tail].y=y;                
                vis[x][y]=sy;
                if(x==n) 
                {
                    water[y]=true;
                    vis1[y]=true;
                    f[sy].l=mini(y,f[sy].l);
                }
            }
        }
    }
    for(int i=f[sy].l;i<=m+1;i++)
    {
    //  cout<<water[i]<<" ";
        if(water[i]!=true) 
        {
            f[sy].r=i-1;
            break;
        }
    }
//  cout<<endl;
}
int main()
{
    n=read();
    m=read();
    int i,j,t=0,end=0;
    for(i=1;i<=n;i++)
    {
        for(j=1;j<=m;j++)
        {
            a[i][j]=read();
        }
    }
    for(i=1;i<=m;i++)
    {
        if(a[1][i]>=a[1][i-1]&&a[1][i]>=a[1][i+1]) flow(i);
        else 
        {
            f[i].l=f[i].r=100000;
        }
        cout<<f[i].l<<" "<<f[i].r<<endl;
    }
    for(i=1;i<=m;i++)
    {
        if(!vis1[i])
        {
            t++;
        }
    }
    if(t>0) printf("0\n%d",t);
    else 
    {
        sort(f+1,f+m+1,cmp);
        for(i=1;i<=m;i++)
        {
            if((f[i+1].l>end+1||f[i+1].r<f[i].r)&&f[i].r>end)
            {
                end=f[i].r;
                ans++;
            }
            if(end>=m) break;
        }
        printf("1\n%d",ans);
    }
    return 0;
}

OVER.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值