题目:传送门
题目概要:有一个n行m列的矩阵,每一个格子都有一个高度,路径只能从高处向低处扩散,问你如果最后一行可以全部被覆盖,最少要从第一行多少个格子开始,如果不能使最后一行全部被覆盖,求有多少个格子不能;
看完这道题,最直接的想法就是直接定义dx,dy两个数组表示上下左右走,看看第一行每一个格子能对应多少个最后一行的格子。
然后再设置一个vis数组表示最后一行是否已经被到达过,如果最后一行有点还没有被到达过,就输出0和vis=0的格子数量
但是当我们想要实现的时候,发现如果第一行的某个点对应的最后一行的点是断断续续的,那就很舒(e)服(xin)了
buuuut~
似乎可以证明,对于每一个第一行的点,他所对应的最后一行的点总是连续的
反证法:假设可以不连续
我们来看图
如图,这是一条从上到下的连续的路径,用红色标记
现在我们假设从第一行开始有这么一条路径不连续,如图,用蓝色表示
我们会发现,这样的话一定会有重合的路径,用紫色表示
既然这样,从第一行蓝色点出发也一定能够到达最下层的红色点
那么最下面一行的区间一定是连续的,证毕(这里不连续是因为有无解的情况)
有了这个结论,只要不是无解的情况,把最后一行的连续区间拿出来,就变成了一个线段覆盖问题
代码:
1 #include<iostream> 2 #include<cstdio> 3 #include<cstdlib> 4 #include<algorithm> 5 #include<cstring> 6 #include<string> 7 #include<queue> 8 #include<stack> 9 #include<time.h> 10 using namespace std; 11 typedef long long ll; 12 ll read(){ 13 ll ans=0; 14 char last=' ',ch=getchar(); 15 while(ch<'0' || ch>'9')last=ch,ch=getchar(); 16 while(ch>='0' && ch<='9')ans=ans*10+ch-'0',ch=getchar(); 17 if(last=='-')ans=-ans; 18 return ans; 19 } 20 21 const int maxn=501; 22 23 int n,m,atlas[maxn][maxn]; 24 int num[maxn][maxn]; 25 bool vis[maxn][maxn]; 26 int dx[4]={1,-1,0,0},dy[4]={0,0,1,-1}; 27 int l[maxn][maxn],r[maxn][maxn]; 28 29 //记忆化搜索 30 void dfs(int x,int y) 31 { 32 vis[x][y]=true;//先标记这个点到达过 33 for(int i=0;i<4;i++)//上下左右搜索 34 { 35 int nx=x+dx[i],ny=y+dy[i]; 36 if(nx<1||nx>n||ny<1||ny>m) continue;//判断边界 37 if(atlas[nx][ny]>=atlas[x][y]) continue;//判断高度 38 if(!vis[nx][ny])dfs(nx,ny); 39 l[x][y]=min(l[x][y],l[nx][ny]); 40 r[x][y]=max(r[x][y],r[nx][ny]);//更新区间左右端点 41 } 42 } 43 44 int main() 45 { 46 n=read(),m=read(); 47 memset(vis,false,sizeof(vis)); 48 memset(l,0x3f,sizeof(l));//初始化 49 for(int i=1;i<=m;i++) 50 { 51 l[n][i]=r[n][i]=i;//区间初始化 52 } 53 for(int i=1;i<=n;i++) 54 { 55 for(int j=1;j<=m;j++) 56 { 57 atlas[i][j]=read(); 58 } 59 } 60 61 for(int i=1;i<=m;i++) 62 { 63 if(!vis[1][i]) dfs(1,i);//如果还没有被到达过,就搜索 64 } 65 66 int counti=0; 67 for(int i=1;i<=m;i++) 68 { 69 if(!vis[n][i]) counti++; 70 }//看最后一行有没有不能到达的 71 if(counti!=0) 72 { 73 cout<<0<<endl<<counti; 74 return 0; 75 } 76 77 int left=1;//记录当前最左边的点 78 while (left<=m)//跑一遍区间覆盖 79 { 80 int maxr=0; 81 for (int i=1;i<=m;i++) 82 if (l[1][i]<=left)//如果这个点在区间左端点的右边 83 maxr=max(maxr,r[1][i]);//寻找右端点最大的 84 counti++; 85 left=maxr+1;//继续更新 86 } 87 cout<<1<<endl<<counti; 88 return 0; 89 }