积水【蓝桥杯 试题集 算法提高】
【90分】(我也不知道为什么 O(1e8)的算法 会T一个点…
判断:数据范围n<=100,图中一共n^2 个点
暴力O(n^4)可行 ⇒ 关键在于如何暴力
核心思路:
形成 “谷地” 才能积水,而判断一个点是否在谷地的方法,即找到他周围的 “包围圈”
思路延申1:
当且仅当这个点无论向哪个方向走走到头,都会遇到比自己高的点时,这个点在包围圈内。 且遇到的比自己高的点即为该点的包围圈的成员
思路延申2:
存在包围圈套包围圈的情况,即如果随机一个点,对它的包围圈进行暴力寻找,遇到比自己高的就停,则有可能找到一个不够大的包围圈。
思路延申3:
基于1,2 。 对于一个谷地点 (x,y),它的积水应该是多少?
(x,y)点 可以向外拓展的 最大”的包围圈的高度 ans[x][y]。
其中包围圈的高度=min( 包围圈上各点的高度)。
该点的积水量为 ans[x][y] - height[x][y]
为避免情况2,我们做一次高度排序,从高到底进行包围圈判断
时间复杂度:
点数N<=10000
排序O( N log N ),
暴搜包围圈,遍历每个点是N级别,对每个点搜索包围圈的最差情况是将N个点全搜,所以 O( N^2 )
实操如下:
#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<cmath>
using namespace std;
inline int read(){
char ch=getchar();bool f=true;int sum=0;
while(ch<'0'||ch>'9'){
if(ch=='-')f=false;ch=getchar();}
while(ch>='0'&&ch<='9'){
sum*=10;sum+=ch-'0';ch=getchar();}
if(f)return sum;else return -sum;
}
#if 0
Goes && G.S.M.
Title://积水 深搜
#endif
const int N=105;
const int INF=(1<<30);
int h[N][N];
int ans[N][N];long long res;//被围住的高度
int n,m;
//包围圈中最低的高度为包围圈高度
//对于每个点,这个点的存水量=ans[i][j]-h[i][j]
//其中ans[i][j]表示所有将该点包围的包围圈中,高度最高的包围圈高度
struct ss{
int x,y,h;
}hh[N*N];
bool cmp(ss a,ss b){
return a.h>b.h;
}//从高点往低点搜,可以保证更高包围圈优先被判断
bool vis[N][N];
int dfs(int x,int y,int va){
if(x<1||x>n||y<1||y>m) return 0;//漏水
vis[x][y]=true;
if(h[x][y]>va){//大于搜索起点的高度,则是包围圈
return max(h[x][y],ans[x][y]);//记忆化 剪枝
//更外围包围圈or已经是最外围
}
int ls=INF;
if(!vis[x+1][y]) ls=min(ls,dfs(x+1,y,va));
if(!vis[x-1][y]) ls=min(ls,dfs(x-1,y,va));
if(!vis[x][y+1]) ls=min(ls,dfs(x,y+1,va));
if(!vis[x][y-1]) ls=min(ls,dfs(x,y-1,va));
return ls;
}
int main(){
memset(ans,-1,sizeof(ans));
n=read();m=read();int ecnt=0;
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++){
h[i][j]=read();
hh[++ecnt]=(ss){i,j,h[i][j]};
}
sort(hh+1,hh+n*m+1,cmp);
for(int i=1;i<=n*m;i++){
memset(vis,false,sizeof(vis));
int x=hh[i].x,y=hh[i].y,va=hh[i].h;
// if(ans[x][y]==-1)
ans[x][y]=dfs(x,y,h[x][y]);
if(ans[x][y]>=h[x][y]) res+=ans[x][y]-h[x][y];
else res+=0;//ans=0表示流出土地,ans<h表示i自己是边界
}
cout<<res<<endl;
return 0;
}