Codevs 1066 引水入城

你要的原题哦!

引水入城( NOIP 2012)

题意原题说的很明白,并不难理解,就是上面的点可以顺高度梯度扩展到下面的点。一种情况是最后一行的点无法被完全访问,此时是求最后一行的无法被访问的点的个数,另一种情况是最后一行的点可以被完全覆盖,而此时就换做求在第一行选择扩展点的最小个数。


题析:其实我并不是今天才遇到这道题,刚碰到这道题时,我犹豫了许久,但最后还是没敢去碰。但实际上,这道题虽说初次做是有一点困难(dalao勿喷!),但在搞懂了这道题的实际做法时,你又会恍然大悟,它其实并不如你想象中的那么复杂(就如一牛说言:这道题即没用高级的结构又没用高级的算法,何谈复杂)

    引入的话还是不写了,既然这道题看起来有点杂乱,那我们就要用划分子问题(?)的方法,一步步搞倒这道题。那就按题目说要求的来做,首先,我们需要考虑如何去求最后一行点的问题。而对于求一个点是否可以被访问的一系列问题,我们想到的,最简单的算法,那就是搜索,所以我们为何不可以干脆将第一行的点的水闸全部放开,让它们自由地输水,而我们只需要枚举一遍最后一行的访问情况,直接下定论就可以了,此处最多的搜索情况为500*500=25000,以BFS+判重数组即可轻松搞定,也不知是不是就是网上所说的灌水法

    还剩下一个求最小用点的问题,对于这个问题,我就不能提太多了,网上一般的解法是把第一行的每个点在最后一行能覆盖的区间(注意,此处的覆盖点为什么呈一个连续区间,需要一定证明)看做一个线段,紧接着就是线段覆盖问题中的一种,求出最小用线段数即可了。这里贴出一些链接,或许有大家在这道题中想要看到的东西:

    百度文库解题报告,在最后有用来证明区间问题:文库1

   百度文库解题报告,最后用了比较常规的方式证明了区间问题,清晰易懂:文库2

    此题所联系的线段覆盖问题,以及其它线段覆盖系列的问题,没看过的同学可以看一下,这一类问题还是别具特色的,这里丢了一个博客,当然也可以自己另外再发掘:线段覆盖

    当然还有各类大牛的博客值得一看,在此不再一一列举。


代码:最后贴上丑陋的代码,请多各牛多多包涵~

#include<queue>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
#define INF 0x3f3f3f3f
using namespace std;

const int N=500+5;

struct Line{ // 线段结构体 
  
    int l,r;
  
    friend bool operator <(Line temp1,Line temp2){
    
        if ( temp1.l!=temp2.l )return temp1.l<temp2.l;
          else return temp1.r>temp2.r;
          
    }
         
};

struct Point{ // 点结构体 
  
    int h,l;
 
};    

// 行列扩展数组 
int dh[5]={0,0,0,-1,1};
int dl[5]={0,-1,1,0,0};
// 题目数据 
int n,m;
int het[N][N];
// 
Line a[N]; // 记录第一行每个点在最后一行的覆盖区间(线段) 
bool vis[N][N]; // 访问记录数组 
queue<Point> q;  // Bfs队列 
//
void Bfs(){

    while( !q.empty() ){ 
    
        Point point=q.front();
        q.pop(); // 取点 
        
        int h=point.h,l=point.l; // 用h,l简化表示 
        
        for(int i=1;i<=4;i++)
          if ( h+dh[i]<=n && h+dh[i]>=0 && l+dl[i]<=m && l+dl[i]>=0 && het[h+dh[i]][l+dl[i]]<het[h][l] && !vis[h+dh[i]][l+dl[i]] ){
// 前四个判断是防止扩展点越界,第五个是为了满足由高到低的要求,第六个是为了防止重复访问同一个点              
              Point npoint; 
              npoint.h=h+dh[i],npoint.l=l+dl[i];  
              q.push(npoint);
              
              vis[h+dh[i]][l+dl[i]]=true; 
          
          }          
    
    }    

  return; 
 
}

int Judge(){  // Bfs搜索所有扩展点,看是否是能使最后行完全访问 
       
    for(int i=1;i<=m;i++){ // 入队所有第一行的点,并标记访问 
      
        Point point;
        point.h=1,point.l=i; 
        q.push(point); // 入队 
        
        vis[1][i]=true;    
    
    }        
    
    Bfs(); // Bfs,超大灌水法,直接找到最后一行的访问可能 
    
    int cnt=0; // 记录最后一行不可能访问点的个数 
        
    for(int i=1;i<=m;i++)
      if ( !vis[n][i] ) cnt++;
    
  return cnt;

}

//

int Work(){
    
    for(int i=1;i<=m;i++){
        
        memset(vis,false,sizeof(vis)); // 默认所点都没访问 
        
        Point point;
        point.h=1,point.l=i; 
        q.push(point); // 入队 
        
        vis[1][i]=true;
        
        Bfs(); // 灌水,对第一行每个点扩展到下面的可能搜索 

//  记录有访问所得到的最后一行访问的最大区间,即一条线段      
        
        Line line; 
        line.l=0,line.r=0;  // 没有太大意义的赋值,为了之后判断而用 
   
        for(int j=1;j<=m;j++)
          if ( vis[n][j] ){   // 从最后一排的第一位开始找,找到第一个可以被访问的点即是线段的起点 
            
              line.l=j;  // 记录该线段的左端点  
              break;
              
          }       
        
        for(int j=a[i].l;j<=m;j++)  
          if ( !vis[n][j+1]  ){ // 从线段的左端点开始找,找到第一个不可以被访问的点之前的一个点即是线段的起点
          
              line.r=j;
              break;
          
          }  
          
        if ( line.r==0 ) line.r==m; // 没有找到线段的左端点,那可知它一定是延长到最后的   
        
        a[i]=line;  

//       
          
    }

//    
    
    sort(a+1,a+m+1); // 给线段排序,靠前的数据,使线段左端尽可能小,此外右端尽可能大 
            
    int k=1,last=0,best,ans=0;  // 从网上搬下来的一段,以贪心法来解决线段覆盖全区间问题,记录第一行最少用点 
      while (last<m) {  
        best=0;  
        for(;a[k].l<=last+1 && k<=m;++k)  
          best=max(best,a[k].r);  
          last=best;  
          ans++;  
      }

//
  
  return ans;      
        
}

//

int main(){

    cin>>n>>m;
    
    for(int i=1;i<=n;i++)
      for(int j=1;j<=m;j++)
        cin>>het[i][j]; // 读入数据 
    
    int wans=Judge();  // 记录最后一行不可能被访问点个数 
    
    if ( wans==0 ){  // 都可以被访问,即不存在不可能被访问的点 
        
        int ans=Work();  // 寻找访问最优解 
         
        cout<<"1"<<"\n"<<ans<<endl;  // 输出可行答案 
    
    }      
      else cout<<"0"<<"\n"<<wans;  // 输出不可行答案 
    
//    while(1);  // 编译器版本低的同学需要的暂停大法 
  
  return 0;     

}
    

抬头一看时间居然是2:33,果然写博客实在是给力啊。


一月已经过去~


2017.2.1


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值