1.题目:
zoj 1083 Frame Stacking
题目大意:有几个大小不一的,且用不同字母表示的框架,他们可以堆叠起来,堆叠后底层的框架有部分将被遮挡。你的任务是读取一个堆叠后的图片,判断框架从底至顶的堆叠顺序。
2.解题思路:
这是一道拓扑排序+回溯+dfs的题。相比于一般的拓扑排序,它要求我们输出所有可能的序列。
首先我们需要用一个结构体表示框架,其中包含了框架的左上角和右下角坐标。在主函数中,一次输入一行字符串,然后对该字符串进行处理。如果发现字母,我们就需要对字母对应的框架进行更新,使这个框架大小尽可能大。
接下来是建立图模型,我们的任务是对已有的框架进行遍历:沿着边沿检查,如果出现其他字母,说明被覆盖了,该字母是其他字母的前置条件(至于为什么后面有提),建立从该字母到其他字母的边,在邻接表中记录,并使那个其他字母的入度加一。最后对图模型进行拓扑排序,由于需要输出所有情况,所有我们需要dfs和记录层数的deep,遇到入度为0的结点时进行递归,另外需要进行回溯,所以使用一个栈s来辅助,需要回退的数量用num记录,结果保存在ans。递归时也需要用到辅助数组opc记录已输出的字母,防止重复输出同个字母。
3.程序清单:
#include<bits/stdc++.h>
using namespace std;
struct frame
{
int ltx,lty,rbx,rby;//左上角和右下角坐标
frame(){ltx=-1;lty=-1;rbx=-1;rby=-1;}
}f[30];
int h,w,cnum,ai;
char p[40][40];
bool c[30],opc[30];//字母表和输出字母表
int indg[30];//入度表
set<int> adl[30];//邻接表
stack<int>s;
char ans[30];
void reset()
{
while(!s.empty()) s.pop();
for(int i=0;i<30;i++)
adl[i].clear();
ai=0;
cnum=0;
memset(c,0,sizeof(c));
memset(f,-1,sizeof(f));
memset(indg,0,sizeof(indg));
memset(opc,0,sizeof(opc));
memset(ans,0,sizeof(ans));
}
inline void edge(int u,int v)
{
if(!adl[u].count(v))
{
adl[u].insert(v);
indg[v]++;
}
}
void create()
{
for(int i=0;i<30;i++)
{
if(c[i])
{
int lx=f[i].ltx,rx=f[i].rbx,ty=f[i].lty,by=f[i].rby;
for(int j=lx;j<=rx;j++)
{
if(p[j][ty]-'A'!=i)//被覆盖,建边
edge(i,p[j][ty]-'A');
if(p[j][by]-'A'!=i)
edge(i,p[j][by]-'A');
}
for(int k=ty+1;k<=by-1;k++)//注意四个角已经判断过
{
if(p[lx][k]-'A'!=i)
edge(i,p[lx][k]-'A');
if(p[rx][k]-'A'!=i)
edge(i,p[rx][k]-'A');
}
}
}
}
void topsort(int deep)
{
if(deep>cnum)
{
for(int k=0;k<ai;k++)
printf("%c",ans[k]);
printf("\n");
return;
}
for(int i=0;i<30;i++)
{
if(c[i]&&!opc[i])
{
if(indg[i]==0)
{
ans[ai++]='A'+i;
opc[i]=1;
int num=adl[i].size();
for(auto j:adl[i])
{
indg[j]--;
s.push(j);
}
topsort(deep+1);
while(num--)//回溯
{
indg[s.top()]++;
s.pop();
}
ai--;
opc[i]=0;
}
}
}
}
inline void updatef(int k,int i,int j)//更新frame范围
{
if(f[k].ltx==-1||f[k].ltx>i)
f[k].ltx=i;
if(f[k].rbx==-1||f[k].rbx<i)
f[k].rbx=i;
if(f[k].lty==-1||f[k].lty>j)
f[k].lty=j;
if(f[k].rby==-1||f[k].rby<j)
f[k].rby=j;
}
int main()
{
while(~scanf("%d%d",&h,&w))
{
for(int i=0;i<h;i++)
{
scanf("%s",p[i]);
for(int j=0;j<w;j++)
{
if(p[i][j]!='.')
{
int k=p[i][j]-'A';
updatef(k,i,j);
if(c[k]!=1)//新结点
{
c[k]=1;
cnum++;
}
}
}
}
create();
topsort(1);
reset();
}
return 0;
}
评测结果:
4.解题总结
这题对于一般的拓扑排序难度有些许提升,因为还要输出所有序列。还好经过条理分析之后写得比较顺利,只WA了一次。导致那一次WA的原因是一开始我没有设置ans数组,以为只要深搜,然后像一般拓扑一样满足条件就输出就行,后来发现想错了,还是需要对序列进行处理。另外对于拓扑排序,课件中的例子是为一些有前置课程的课程安排时间表,其实也可以活用于这题。我们可以将这题想象成一个物品堆叠问题,需要先取出顶部的物品才能取走底部的物品,只是我们的视角是从底部往上看的,这样,对于我们在这题中如何建立、为什么这样建立图模型就很容易理解了。