比赛链接:http://codeforces.com/contest/1089
题意是这样的:
你需要将一张由
n
n
n个节点构成的有向图,转化为一个三维的地图。(1
≤
n
≤
\leq n \leq
≤n≤ 9)
要求对于任意一对节点,在这两个图中的可达性是相同的。
在三维地图中的移动方式有:水平方向移动1格,如果正下方的格子是空的就会自动下落,另外如果头顶的格子是空的还可以爬高一格。
举个例子:
在上图中,假设有数字的格子都是空的。那么我们从1号格子出发,可以先向左走两格,然后爬高一格到达2号格子,或者下落一层到达4号格子,反过来从2号格子或者4号格子走到1号格子也是可以的。也就是说,1号、2号、4号格子是互相可达的。但是3号格子因为所处的位置太高,只能从高处向下落,但不能从低处向上爬,因此从3号格子出发只能单向到达1号、2号、4号格子。
另外,题目样例给出的情况是这样的:
这个图有点难懂,图中每个数字其实表示的是上方的那个格子,对照输出才发现,4号格子其实是最顶层的,1号格子是中间层的。1号格子和4号格子相邻并且高度差为1,因此可以互相到达。2号格子和3号格子都位于最底层,也可以互相到达。另外由于高度差的关系,只能从4号格子所在层出发掉落到最底层,而最底层无法回到高层,所以这是一个单向到达的关系。
显然,这是一道构造的题目。
那么怎么构造呢?
刚看到这个题的时候,首先就会想到,我们可以用Tarjan求出图中的强联通分量,把能够互相到达的节点缩成一个点,最后只需要把他们紧挨着排列在一起就可以。
然后我们得到了一个有向无环图,可以使用拓扑排序,这样我们只能从拓扑序更靠前的节点,单向到达拓扑序更靠后的节点。这在三维地图中可以通过高度差来实现。
也就是说,我们构造的地图是由多个“层”组成的。按照拓扑排序的结果,把拓扑序靠前的强联通分量放在更高层,每个强联通分量内的节点都放在同一层,相邻的层之间保持超过两格的高度差。构造出来的地图是这样的感觉:
不过还存在一些问题,比如,从一个层出发能够到达多个层,并且这多个层之间是不可达的情况,应该如何处理。
这个题给的地图尺寸上限挺大的,于是突然想到了这样一种构图方法:
假设缩点后的强联通分量共有
m
m
m个,那么3维地图总共就有竖直堆叠的m层构成。每一层的长度都是2
m
m
m2,宽度都是4,高度都是3。由一个长条状的“站台”,
m
m
m2个“通道”,以及
m
m
m2个“开关”构成。带数字的格子都位于站台之上,对于任意两层,都有一个专门的通道与之对应。俯视图可能会看的更清楚一些。
大致的思路就是,我们首先要让任意两层都不可到达,然后如果要从层A要到达层B,只能走专门设计的通道。这样的通道总共有
m
m
m2个。
对于样例来说,我们首先把1号和4号节点放在第一层,2号和3号节点放在第二层。由于强联通分量1可以单向到达2,所以我们把从层1到达层2的“通道1-2”打开。也就是从层1的站台出发,穿过像门一样的开关,走到通道处,可以竖直掉落到层2中对应的位置,但是反向的过程无法实现。
我们可以再看一个层数更多的例子:
如果从层A可以单向到达层C,我们就要打开“通道A-C”,然后把A和B对应位置的通道挖开,这样保证可以竖直掉落到层C。然后开启层A和层C对应通道的大门,但是层B的开关要关闭,否则从层B也可以经过这个通道掉落到层C。
如果层B还可以到达层D之间,那就同样找到“通道B-D”,用上述方法建立通道即可。
不同的通道之间是互不干扰的,这也就是为什么要准备
m
m
m2个通道,并且通道之间还要保证空隙。
上述方法构建的地图,长度最大为200,宽度为4,高度最大为30,所以格子数的上限是24000,小于题目要求的上限1e6。
另外还有两个坑:
(1)每一层的长度还有下限是10,因为有可能
n
n
n个点全被缩进同一个强联通分量,如果站台长度只有2是放不下
n
n
n个点的。
(2)最底层的站台需要垫高,因为从任意一层都可以先掉落到整张地图的最底部,然后向上爬一格回到站台。
这道题在比赛结束前的最后9秒钟顺利AC,真的是紧张刺激。
因为时间来不及了,所以代码写的有点丑:
#include<stdio.h>
#include<string.h>
#include<vector>
#include<algorithm>
using namespace std;
int con[12][12],conj[12][12];
vector<int> node[12];
bool instack[12];
int index,top,stack[12],dfn[12],low[12];
int num,belong[12],arr[12],pst[12],indeg[12];
char ans[32][6][210];
bool vis[12];
void Tarjan(int u){
int cnt=node[u].size();
instack[u]=true;
stack[++top]=u;
dfn[u]=low[u]=++index;
for(int i=0;i<cnt;i++){
int v=node[u][i];
if(!dfn[v]){
Tarjan(v);
if(low[v]<low[u]) low[u]=low[v];
}else{
if(instack[v] && dfn[v]<low[u]) low[u]=dfn[v];
}
}
if(dfn[u]==low[u]){
num+=1;
while(true){
int pst=stack[top--];
instack[pst]=false;
belong[pst]=num;
if(pst==u) break;
}
}
return ;
}
void solve(int n){
top=num=index=0;
memset(dfn,0,sizeof(dfn));
memset(low,0,sizeof(low));
for(int i=1;i<=n;i++){
if(!dfn[i]) Tarjan(i); // 强联通分量
}
return ;
}
void Tsort(int m){
int k=0; // 拓扑排序
for(int retry=0;retry<=m;retry++){
for(int i=1;i<=m;i++){
if(indeg[i]==0){
for(int j=1;j<=m;j++){
if(conj[i][j]==1) indeg[j]-=1;
}
pst[i]=k; arr[k++]=i;
indeg[i]=-1;
}
}
if(k==m) break;
}
}
void init(int m,int n){
int len=max(2*m*m,12); // 初始化各层
for(int k=0;k<m;k++){
for(int i=0;i<len;i++){
for(int j=0;j<=3;j++){
ans[3*k][j][i]=ans[3*k+1][j][i]=ans[3*k+2][j][i]='.';
}
ans[3*k+2][0][i]='#'; // 站台
if(i&1) continue;
ans[3*k][1][i]=ans[3*k+1][1][i]=ans[3*k+2][1][i]='#'; // 开关
ans[3*k+2][2][i]=ans[3*k+2][3][i]='#'; // 通道
}
for(int i=1;i<=n;i++){
if(belong[i]==arr[k]){
ans[3*k+1][0][i]='0'+i;
}
}
}
for(int k=3*m;k<3*m+2;k++){
for(int i=0;i<len;i++){
for(int j=0;j<=3;j++){
ans[k][j][i]='.'; // 最底部垫高两层
}
}
}
}
int main(){
int n,lift;
while(scanf("%d",&n)!=EOF){
memset(conj,0,sizeof(conj));
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
scanf("%d",&con[i][j]);
if(con[i][j]==1){
node[i].push_back(j);
}
}
}
solve(n);
memset(indeg,0,sizeof(indeg));
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
if(con[i][j]==1 && belong[i]!=belong[j]){
if(conj[belong[i]][belong[j]]==0) indeg[belong[j]]+=1;
conj[belong[i]][belong[j]]=1;
}
}
}
Tsort(num);
init(num,n);
for(int i=1;i<=num;i++){
for(int j=1;j<=num;j++){
if(conj[i][j]==1){
lift=2*((i-1)*num+(j-1)); // 打开通道
ans[3*pst[i]][1][lift]=ans[3*pst[i]+1][1][lift]='.';
ans[3*pst[j]][1][lift]=ans[3*pst[j]+1][1][lift]='.';
for(int k=pst[i];k<pst[j];k++){
ans[3*k+2][3][lift]='.';
}
}
}
}
int len=max(2*num*num,12);
printf("%d %d %d\n",len,4,3*num+2);
for(int k=0;k<3*num+2;k++){
if(k!=0) printf("\n");
for(int i=0;i<4;i++){
ans[k][i][len]='\0';
printf("%s\n",ans[k][i]);
}
}
for(int i=1;i<=n;i++){
node[i].clear();
}
}
return 0;
}