2010年NOIP全国联赛提高组 T4 引水入城

题目链接:这是codevs,其他各大OJ都有,只是本人喜欢Codevs

题目中有图片,建议大家去原处看图之后再来看题解……

题解:
看完题目后,首先想什么情况下无解,那不如先假设第一排全部建设蓄水厂,然后搜索一下有没有城市喝不到水,这样复杂度是O(地图大小QAQ)的,500*500绰绰有余(记忆化的前提下)。

for(int i = 1;i <= m;i ++)  if(!vis[1][i])  bfs1(1,i);
bool flag = 0;
int cnt = 0;
for(int i = 1;i <= m;i ++)
{
    if(!vis[n][i])
    {
        flag = 1;
        cnt ++;
    }
}
if(flag){printf("0\n%d",cnt);   return 0;}

经过上面的判断之后,如果所有城市都能喝上水,这说明题目一定有解,那么下面对有解的情况进行分析:
如果题目有解,那么建蓄水厂的地方一定能覆盖一个区间,一定!
你可能回担心下面这种情况:

/*
10 1 1 12 1 1
9  1 5 4  3 1
8  7 6 11 2 1
*/

但是仔细分析会发现上面情况就是无解的,12覆盖的区间不如10覆盖的区间多。
于是我们大概可以确定一个蓄水厂必须覆盖一段连续的区间。
那么我们的目的转化为让l尽可能小,r尽可能大,那么如何实现呢?
我们用爬山的办法来转换,刚才是从上往下灌水,现在就从底层爬山,当处理最小的l的时候就从底层1往m依次搜索,由于搜索顺序从1~m,所以左边的一定比右边的优(如果能搜到的话),那么就可以加记忆化了:一个点只搜一遍,搜到顶层的某个点的时候吧某个点的l设置为当前搜索的底层点的纵坐标(点是第一层的所有点,不用开二维),r的时候恰好反过来……,晕了吧……看样例:

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

8向上爬山爬到9,9在第一层,所以现在9的左端点就变成了1;
7向上爬山爬到8,由8再爬到9,但是我们记忆化过,所以上面过程在爬到8的时候已经停止了。
6向上爬到7,由于记忆化还是停止了……
……
……
2向上爬山爬到3,但是3在1爬山时已经走过,或者说2已经被走过,所以2不搜。

右端点同理:
2爬山一直爬到了9,直接更改9的坐标和路径上第一行5,4,3的坐标,然后之后的点大致都不搜了。

处理完这些后每个点的l和r就是一个线段,最少蓄水厂就是最少线段覆盖整个区间问题,这个问题可以DP,也可以贪心,这里我选择贪心(DP也不难……):
首先双关键字排序:l和r都尽可能小。
now表示当前右端点,to表示能扩展的最远点,ans表示需要的线段数(即蓄水厂数),然后从1号边开始,直到一条线段的左端点大于当前右端点时,就扩展到to点,即最远点,能扩展的最远点再更新为当前线段的to,直到所有边遍历结束

但是有可能不能覆盖所有部分,所以最后要加特判:

if(now != m)    ans ++;

至此 这个题就解决了,但是交上去90分?
天坑:n == 1。
……你一边临近湖一边临近沙漠什么鬼啊……
这时候需要在bfs中加一些特判,即起点终点在一行的情况,加了特判后就ac了QAQ
下面详见代码:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
#include<algorithm>
#include<stack>
#define QAQ puts("ArchonOrz");
using namespace std;
const int size = 555;
const int INF = 233333;
int mps[size][size],n,m;
int step1[]={0,1,-1,0,0};
int step2[]={0,0,0,1,-1};
bool vis[size][size];
struct Poi{int x,y;};
queue <Poi> q;
void bfs1(int sx,int sy)
{
    q.push((Poi){sx,sy});
    vis[sx][sy] = 1;
    while(!q.empty())
    {
        Poi f = q.front();
        q.pop();
        for(int i = 1;i <= 4;i ++)
        {
            int x = f.x + step1[i];
            int y = f.y + step2[i];
            if(x < 1 || x > n || y < 1 || y > m || mps[x][y] >= mps[f.x][f.y] || vis[x][y]) continue;
            vis[x][y] = 1;
            q.push((Poi){x,y});
        }
    }
}
//bfs2 x -> l   y -> r 即控制区间 
Poi po[size];
void bfs2(int sx,int sy)
{
    if(sx == 1 && po[sy].x > sy)    po[sy].x = sy;
    q.push((Poi){sx,sy});
    vis[sx][sy] = 1;
    while(!q.empty())
    {
        Poi f = q.front();
        q.pop();
        for(int i = 1;i <= 4;i ++)
        {
            int x = f.x + step1[i];
            int y = f.y + step2[i];
            if(x < 1 || x > n || y < 1 || y > m || mps[x][y] <= mps[f.x][f.y] || vis[x][y]) continue;
            if(x == 1 && po[y].x > sy)  po[y].x = sy;
            vis[x][y] = 1;
            q.push((Poi){x,y});
        }
    }
}
void bfs3(int sx,int sy)
{
    if(sx == 1 && po[sy].y < sy)    po[sy].y = sy;
    q.push((Poi){sx,sy});
    vis[sx][sy] = 1;
    while(!q.empty())
    {
        Poi f = q.front();
        q.pop();
        for(int i = 1;i <= 4;i ++)
        {
            int x = f.x + step1[i];
            int y = f.y + step2[i];
            if(x < 1 || x > n || y < 1 || y > m || mps[x][y] <= mps[f.x][f.y] || vis[x][y]) continue;
            if(x == 1 && po[y].y < sy)  po[y].y = sy;
            vis[x][y] = 1;
            q.push((Poi){x,y});
        }
    }
}
struct Edge{int from,to;}edges[size];
int head[size],next[size],tot;
bool cmp(Edge a,Edge b)
{
    return a.from == b.from ? a.to < b.to : a.from < b.from;
}
void build(int f,int t)
{
    edges[++tot].to = t;
    edges[tot].from = f;
    next[tot] = head[f];
    head[f] = tot;
}
int main()
{
    scanf("%d%d",&n,&m);
    for(int i = 1;i <= n;i ++)
        for(int j = 1;j <= m;j ++)
            scanf("%d",&mps[i][j]);
    for(int i = 1;i <= m;i ++)  if(!vis[1][i])  bfs1(1,i);
    bool flag = 0;
    int cnt = 0;
    for(int i = 1;i <= m;i ++)
    {
        if(!vis[n][i])
        {
            flag = 1;
            cnt ++;
        }
    }
    if(flag){printf("0\n%d",cnt);   return 0;}
    memset(vis,0,sizeof(vis));
//  QAQ
    for(int i = 1;i <= m;i ++)  po[i].x = INF,po[i].y = -INF;
    for(int i = 1;i <= m;i ++)  if(!vis[n][i])  bfs2(n,i);
    memset(vis,0,sizeof(vis));
//  QAQ
    for(int i = m;i > 0;i --)   if(!vis[n][i])  bfs3(n,i);
//  QAQ
//  for(int i = 1;i <= m;i ++)  printf("%d %d\n",po[i].x,po[i].y);
    for(int i = 1;i <= m;i ++)  if(po[i].x != INF && po[i].y != -INF)   build(po[i].x,po[i].y);
//  for(int i = 1;i <= tot;i ++)cout<<edges[i].from<<" "<<edges[i].to<<endl;
    sort(edges+1,edges+1+tot,cmp);
    int now = 0,ans = 0,to = 0;
    for(int i = 1;i <= tot;i ++)
    {
        if(now+1 >= edges[i].from)  to = max(to,edges[i].to);
        else now = to,to = max(to,edges[i].to),ans ++;
    }
    if(now != m)    ans ++;
    printf("1\n%d\n",ans);
    system("pause");
    return 0;
}

蒟蒻Orz各位大神






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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值