题解 P5198 【[USACO19JAN]Icy Perimeter】

单纯求联通块的一道题,种子填充算法可以简单的解决。

种子填充就不介绍了,这个都不知道还敢做蓝题……

然后是不好搞的求周长部分,对于递归中的每个点,如果周围是未编号节点,大小加一并且进入该点,如果周围是空地,周长加一。

代码很简单,仅供参考。

#include<bits/stdc++.h>
using namespace std;
int n,siz,len,anss,ansl;//len与siz为当前联通块周长和大小,anss和ansl为最终联通块大小和周长
char inp;
bool pic[1005][1005],mk[1005][1005];//pic数组存储图,mk数组储存某点是否被遍历过
void dfs(int a,int b){
    mk[a][b]=1;
    siz++;
    for(int i=-1;i<=1;i++)
        for(int j=-1;j<=1;j++)
            if(abs(i)!=abs(j)){
                if(pic[a+i][b+j]==1&&!mk[a+i][b+j])//如果是个未编号节点,递归进入。
                    dfs(a+i,b+j);
                if(pic[a+i][b+j]==0)//如果是个空节点,周长加一
                    len++;
            }
}
int main(){
    cin>>n;
    for(int i=1;i<=n;i++)
        for(int j=1;j<=n;j++){
            cin>>inp;
            if(inp=='#')
                pic[i][j]=1;
        }
    for(int i=1;i<=n;i++)
        for(int j=1;j<=n;j++)
            if(!mk[i][j]&&pic[i][j]){
                siz=0;
                len=0;
                dfs(i,j);
                // cout<<siz<<" "<<len<<'\n';
                if(siz>anss){//对大小和周长做最大值处理
                    anss=siz;
                    ansl=len;
                }
                else if(siz==anss)
                    ansl=min(ansl,len);
            }
    cout<<anss<<' '<<ansl;
}

但是,接下来是关键部分。

我们发现当前情况下递归的层数最大达到了$10^6$,虽然洛谷让你过了,可是万一在某些少爷机上突然爆栈了……(爆零的恐惧)

接下来我们学习如何防止在递归过程中爆栈。

在代码开头添加如下片段:

#pragma comment(linker, "/STACK:102400000,102400000") 

和/或

#pragma GCC optimize("no-stack-protector")

即可!

好吧我们接下来来学习如何手动模拟递归过程以求不使用递归完成dfs。

在数据结构基础上我们学过递归的原理是利用栈空间来完成数据的传输。

然而,这个栈空间的大小(只有几M)远远小于堆空间的大小(总体可调用的在G以上),于是我们手写数据结构把栈移到堆里头。具体实现见代码。

#include<bits/stdc++.h>
using namespace std;
int n,siz,len,anss,ansl,top;//len与siz为当前联通块周长和大小,anss和ansl为最终联通块大小和周长
pair<int,int> sta[1000005];//Stack! first存储行,second存储列
char inp;
bool pic[1005][1005],mk[1005][1005],lmk[1005][1005][3][3];//pic数组存储图,mk数组储存某点是否被遍历过
int main(){
    cin>>n;
    for(int i=1;i<=n;i++)
        for(int j=1;j<=n;j++){
            cin>>inp;
            if(inp=='#')
                pic[i][j]=1;
        }
    for(int i=1;i<=n;i++)
        for(int j=1;j<=n;j++)
            if(!mk[i][j]&&pic[i][j]){
                siz=1;
                len=0;
                int a,b,p;
                sta[++top]=make_pair(i,j);
                mk[i][j]=1;
                while(top){
                    a=sta[top].first;
                    b=sta[top].second;//传入栈中的值
                    p=1;
                    bool flag=0;
                    for(int k=-1;k<=1;k++){
                        for(int l=-1;l<=1;l++)
                            if(abs(k)!=abs(l)){
                                if(pic[a+k][b+l]&&!mk[a+k][b+l]){
                                    sta[++top]=make_pair(a+k,b+l);//这是难点,将递归参数写进栈里面
                                    mk[a+k][b+l]=1;
                                    siz++;
                                    flag=1;//这是难点,如何暂停当前循环并且在完成下层之后使之恢复原状态
                                    break;
                                }
                                if(pic[a+k][b+l]==0&&!lmk[a][b][k+1][l+1]){
                                    len++;
                                    lmk[a][b][k+1][l+1]=1;
                                }
                            }
                        if(flag)
                            break;
                    }
                    if(flag)
                        continue;
                    top--;
                }
                if(siz>anss){//对大小和周长做最大值处理
                    anss=siz;
                    ansl=len;
                }
                else if(siz==anss)
                    ansl=min(ansl,len);
            }
    cout<<anss<<' '<<ansl;
}

比直接递归麻烦的不是一点半点,效率上同样有差别,但不是太大。

关键点就是如何暂停当前层的循环并且在完成它的子层循环后使之恢复到原状态。我采取的方法是这样,对于已经处理过的地方打上标记,在处理完子层循环后对于已经标记过的地方不做处理,以使之只处理上一次处理过的位置的下一个。

配上Submit截图:

递归
递归

非递归
非递归

好吧最后说句题外话,对于这些保证需要全部遍历图的的搜索,推荐使用BFS以避免爆栈,然而楼上大佬写过了我就不写了。

推销一下我的博客!本文会在博客园同步更新。
Blog of Schwarzkopf-Henkal

完(是真的)

I'm Schwarzkopf Henkal.

猜你喜欢

转载自www.cnblogs.com/Schwarzkopf-Henkal/p/11737247.html