1 条题解
-
0
自动搬运
来自洛谷,原作者为

☆木辛土申☆
我要变强啊~~搬运于
2025-08-24 22:17:02,当前版本为作者最后更新于2020-02-13 17:31:39,作者可能在搬运后再次修改,您可在原文处查看最新版自动搬运只会搬运当前题目点赞数最高的题解,您可前往洛谷题解查看更多
以下是正文
这里先讲一下FloodFill算法
(已了解的请自行跳过)
FloodFill算法就是一种用指定的颜色填充某个密闭区域的算法,一种分割图像的基本算法,就相当于于画图中的油漆桶,一倒泼一片的那种,可以用来单纯的“填颜色”,或者求连通块。我通常用BFS算法实现,当然也可以用DFS,看个人喜好啦。 P.S这里推荐一个讲的比较好的博客--->泛洪算法.
现在看两道道例题啦
这两道题目都是基础的填色题,我们考虑向外面泼一格水,让水它自由的流,遇到障碍就停止,此时没有被水覆盖到的部分便是我们要求的答案。
但是我们现在有一个问题,那格水该怎么泼呢?
这里提供两种方法:
- 1.在读入数据的过程中特判边界点存入队列,这里以P1506为例:
for(int i=1;i<=n;i++){ for(int j=1;j<=m;j++){ read(map[i][j]); if((i==1||j==1||i==n||j==m)&&map[i][j]=='0'){ q.push(std::make_pair(i,j)); vis[i][j]=true; } } }//map是数组,不是std::map,个人不太喜欢使用万能头- 2.拓宽边界,建立虚拟边界,将水泼在边界外与虚拟边界之间的一点,这里将越界条件改一下(需要数组从1开始存信息):
bool illegal(int x,int y){ return x<1||x>n||y<1||y>n||vis[x][y]; }//这是原来的条件 bool illegal(int x,int y){ return x<0||x>n+1||y<0||y>m+1||vis[x][y]; }//这是更改后的条件这样使用时可直接调用bfs(0,0)或dfs(0,0)而不用特判边界点了。
边界特判处理完后,代码就很容易实现了。这里给出一种普遍实现:
//方向常量数组 const int fx[]={}; const int fy[]={}; //这里{}内枚举可能的方向 struct Node{ //定义节点信息,如坐标信息 //定义构造函数(可选) //定义比较函数(如本题) } //定义越界判断函数 bool illegal(int x,int y); //定义队列 //搜索函数 bfs(int x,int y){ while(队列不为空){ 取出队首节点; for()扩展节点并判断; 更新信息; 如果符合条件加入队列; } } int main(){ bfs(); 输出答案; return 0;//一定别忘了 }
关于本题
本题本质上也是一个FloodFill,但实现上有略微不同。
仔细阅读,题意很简单,一个矩阵上的所有点上都有一个立方体柱,求这些立方体柱构成的容器最多能盛装多少液体。
首先我们可以想到一种朴素做法:
对每一个高度的平面进行一次FloodFill,但这样做时间复杂度太高,高度在10^9级别,再乘上平面单次FloodFill的O(n^2)n_max=300,显然tle了。
既然有了思路还可以改一下啦
我们对高度进行离散化,离散化后的水平面最多有300*300=90000个,每个平面进行一次FloodFill,加上离散化查找的复杂度,虽比第一种方法优秀许多,但仍然是妥妥的tle(实测能得53分,
数据真是太水了。。。。(bushi))接下来是正解啦(我先放代码了)
我想了好久才想出正解,我真是太蒻了。。(认真.jpg)
#include<cstdio> #include<cctype> #include<algorithm> #include<queue> typedef long long LL; const int SIZE=301; const int fx[]={0,0,0,1,-1}; const int fy[]={0,1,-1,0,0}; int map[SIZE][SIZE],h,w; bool vis[SIZE][SIZE]; struct Node { int x,y,z;//x是行,y是列,z是高度 Node(const int &key1,const int &key2,const int &key3):x(key1),y(key2),z(key3) {} inline bool operator <(const Node &in)const { return in.z<this->z; } }; template<typename T>inline void read(T &res){ res=0;char ch=getchar();bool neg=false; for(;!isdigit(ch);ch=getchar()) if(ch=='-') neg=true; for(; isdigit(ch);ch=getchar()) res=(res<<1)+(res<<3)+(ch^48); if(neg) res=-res; } inline bool illegal(int x,int y){ return x<1||x>h||y<1||y>w||vis[x][y]; } std::priority_queue<Node> q; inline void init(int w,int h){ for(int i=1;i<=h;i++){ for(int j=1;j<=w;j++){ read(map[i][j]); if(i==1||j==1||i==h||j==w){ q.push(Node(i,j,map[i][j])); vis[i][j]=true; } } } } inline LL floodfill(){ LL res=0; while(!q.empty()){ int x=q.top().x; int y=q.top().y; int h=q.top().z; q.pop(); for(int i=1;i<=4;i++){ int xx=x+fx[i]; int yy=y+fy[i]; if(illegal(xx,yy)) continue; if(map[xx][yy]<h) res+=h-map[xx][yy],map[xx][yy]=h; q.push(Node(xx,yy,map[xx][yy])); vis[xx][yy]=true; } } return res; } signed main() { read(w),read(h),init(w,h); printf("%lld\n",floodfill()); return 0; }代码相信大家看后都很好理解的,可为什么这样是对的呢。
这个问题的重点是计算顺序。如果我们每次选择边界上最低的点开始FloodFill则果汁一定不会洒出去,比它低的点显然可以盛到液体,而高的点会成为新的边界,于是我们用一个优先队列来维护,每次取高度最低的点进行扩展,就可以了。
在这里感谢这篇博客USACO 05Gold给予了我思路,上面的思路十分直接,我真是太蒻了(强调)。
相信之后一定会有写的比我好的多的题解,如果通过的话,这就是我的第一篇题解了。
另外,欢迎大家来扩列啊(^▽^)。
- 1
信息
- ID
- 5092
- 时间
- 1000ms
- 内存
- 125MiB
- 难度
- 4
- 标签
- 递交数
- 0
- 已通过
- 0
- 上传者