题解 P6063 【[USACO05JAN]The Wedding Juicer G】

· · 题解

这里先讲一下FloodFill算法

(已了解的请自行跳过)

FloodFill算法就是一种用指定的颜色填充某个密闭区域的算法,一种分割图像的基本算法,就相当于于画图中的油漆桶,一倒泼一片的那种,可以用来单纯的“填颜色”,或者求连通块。我通常用BFS算法实现,当然也可以用DFS,看个人喜好啦。 P.S这里推荐一个讲的比较好的博客--->泛洪算法.

现在看两道道例题啦

这两道题目都是基础的填色题,我们考虑向外面泼一格水,让水它自由的流,遇到障碍就停止,此时没有被水覆盖到的部分便是我们要求的答案。

但是我们现在有一个问题,那格水该怎么泼呢?

这里提供两种方法:

这样使用时可直接调用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给予了我思路,上面的思路十分直接,我真是太蒻了(强调)。

相信之后一定会有写的比我好的多的题解,如果通过的话,这就是我的第一篇题解了。

另外,欢迎大家来扩列啊(^▽^)。