这个题很好判断灌溉多少个点,一遍bfs就过了,难点在于,这个题要求我们求在最后一行所有点都被浇灌到的时候最少需要建几个水站,因此我们要求每个水站的浇灌范围,我们可以比较容易的想到,如果可以全部浇灌的时候,每个水站所能浇灌的范围是一条线段,假如说最后一行所有点都可以被浇灌,其中一个水站扩展出去的线段被分成两个部分,那么一定有另一个水站的水与这个水站的水流有交点,那么这个水站的水流也能流到中间,所以这种情况是不存在的。问题最后转化成了一个线段之间可以相交的线段覆盖。我们要想求所有的线段,就要以第一行每个点做起点,进行bfs求水站的扩展范围,这里我们可以做一个小剪枝,所有高度小于左边的点都不用以它为起点进行bfs,因为无论如何,水都可以从它左边的点流向它。
求出线段后,我们用贪心的思想来解决问题,因为可以重叠,所以我们应该选取尽可能右端点靠右的点(很好理解)。所以我们不断选取线段,直至最右的点为n的时候停下。之前的预处理最坏情况为o(nm^2),但是因为剪枝,所以大部分情况要更优
#include<algorithm> #include<iostream> #include<cstring> #include<cstdio> #include<cmath> #include<queue> using namespace std; struct in { int x,y; }ter[550]; queue<in>qwq; int n,m,mmp[550][550],tot,ci,fx[5]={0,-1,0,1,0},fy[5]={0,0,1,0,-1}; bool flag[550][550],fl[550]; inline void bfs() { while(!qwq.empty()) { in qaq=qwq.front(); if(qaq.x==n) { if(!(fl[qaq.y]))//如果这个点作为最后一排并且之前没到过,说明又多了一个可以走到的点 tot--,fl[qaq.y]=1; ter[ci].x=min(ter[ci].x,qaq.y),ter[ci].y=max(ter[ci].y,qaq.y);//处理出线段来 } for(int i=1;i<=4;i++) { in to=qaq; to.x+=fx[i],to.y+=fy[i]; if(to.x<1||to.x>n||to.y<1||to.y>m) continue; if(mmp[qaq.x][qaq.y]>mmp[to.x][to.y]&&(!flag[to.x][to.y])) qwq.push(to),flag[to.x][to.y]=1; } qwq.pop(); } } bool cmp(in a,in b) { if(a.x==b.x)//因为我们贪心的思路是,尽可能让右边多扩展一点 return a.y>b.y; return a.x<b.x; } void solve() { cout<<1<<'\n';//cout在不输出endl的时候是要比printf更快的,至少个人亲测如此 sort(ter+1,ter+1+ci,cmp); int ans=1,tail=ter[1].y,lin=tail,ta=0; while(tail<m) { for(int j=1;j<=ci;j++)//遍历一遍所有的边,因为线段之间可以重复 { if(ter[j].x<=tail+1&&ter[j].y>lin)//尽可能向右扩展 lin=max(lin,ter[j].y),ta=j; } tail=lin; ans++; } cout<<ans; } int main() { scanf("%d%d",&n,&m); for(int i=1;i<=n;i++) for(int j=1;j<=m;j++) scanf("%d",&mmp[i][j]); tot=m; for(int i=1;i<=m;i++) { if(mmp[1][i]<mmp[1][i-1])//一定可以从(1,i-1)流过来 continue; memset(flag,0,sizeof(flag)); qwq.push((in){1,i}),flag[1][i]=1,ter[++ci]=(in){1000000007,-1000000007},bfs(); if(ter[ci].x==1000000007&&ter[ci].y==-1000000007)//防止有的点到达不了最后一排却占一个线段的位子 ci--; } if(tot) cout<<0<<'\n'<<tot; else solve(); }