题解:[SDOI2015] 立体图
约定
记 \((i,j,k)\) 表示第 \(i\) 行第 \(j\) 列从下往上数第 \(k\) 个正方体的位置,其实就是一个右手坐标系。
同时记 \(h_{i,j}\) 表示第 \(i\) 行第 \(j\) 列最高的正方体的高度。
记 top、front、right 分别表示正方体的顶面、正面、右面。top[1]、top[2]、top[3]、top[4] 分别表示按照逆时针顺序编号的三角形(参见下文),front、right 同理。
颜色混合
考虑状压,位运算 | 维护颜色混合即可。
可以定义颜色类 color:
constexpr const char colorList[8]={'K','B','G','C','R','P','Y','W'};
struct color{
int rgb;
color(){
rgb=0;
}
color(int x){
rgb=x;
}
color(char ch){
rgb=0;
for(int i=0;i<8;i++){
if(ch==colorList[i]){
rgb=i;
break;
}
}
}
char ch(){
return colorList[rgb];
}
};
color operator |(color a,color b){
return a.rgb|b.rgb;
}
color operator |=(color &a,color b){
return a=a|b;
}
唯一要注意的可能是默认为黑色(K)。
图形存储
一个正方体的表面可以划分为 \(3\) 个正方形,每个正方形划分为 \(4\) 个三角形,共 \(12\) 个三角形。
为了方便,将三角形按照逆时针编号:

并且称正方体的顶面为 top,正面为 front,右面为 right。
使用 h[i][j] 来记录第 \(i\) 行第 \(j\) 列的高度,可有结构体:
constexpr const int N=100,M=100,H=100;
int n,m,h[N+1][M+1];
struct cube{
color top[5],front[5],right[5];
char query(char ch){
if('1'<=ch&&ch<='4'){
return top[ch-'0'].ch();
}else if('5'<=ch&&ch<='8'){
return front[ch-'4'].ch();
}else if(ch=='9'){
return right[1].ch();
}else if('A'<=ch&&ch<='C'){
return right[ch-'A'+2].ch();
}else{
return ch;
}
}
}a[N+1][M+1][H+1];
a[i][j][k] 表示第 \((i,j,k)\) 的正方体的颜色。
为了能够在渲染方块的时候快速对应到对应的 color 类,可以对正方体进行编码:
constexpr const char shape[13][14]={
"####+-------+",
"###/2\\1111'/|",
"##/22.*'44/9|",
"#/.3333\\4/9/|",
"+-------+9.C|",
"|\\55555/|\\:C|",
"|6\\555/8|A*C|",
"|66\\5/88|A:\\|",
"|666X888|A'B+",
"|66/7\\88|/B/#",
"|6/777\\8|B/##",
"|/77777\\|/###",
"+-------+####"
};
查询逻辑参见 cube.query(ch)。
渲染方块
考虑从后往前渲染,这样前面的直接覆盖住后面的。
至于实际存储中可能会面临不知道这个方块在实际存储答案的数组中的位置,可以直接给它的行标、列标都加上 \(1000\) 的偏移量,输出的时候直接输出有效部分即可。
constexpr const int row=1e4,col=1e4;
static char canvas[row+1][col+1];
int posL=col+1,posR=-1,posU=row+1,posD=-1;
void place(int i,int j,cube x){
i+=1000,j+=1000;
for(int pi=0;pi<13;pi++){
for(int pj=0;pj<13;pj++){
if(shape[pi][pj]=='#'){
continue;
}
canvas[i+pi][j+pj]=x.query(shape[pi][pj]);
posL=min(posL,j+pj);
posR=max(posR,j+pj);
posU=min(posU,i+pi);
posD=max(posD,i+pi);
}
}
}
void Print(){
for(int i=1,posI=1;i<=n;i++,posI+=4){
for(int j=1,posJ=(n-i)*4+1;j<=m;j++,posJ+=8){
for(int k=1;k<=h[i][j];k++){
place(posI-8*(k-1),posJ,a[i][j][k]);
}
}
}
for(int i=posU;i<=posD;i++){
int r=posR;
while(!canvas[i][r]){
r--;
}
for(int j=posL;j<=r;j++){
if(canvas[i][j]==0){
cout<<' ';
}else{
cout<<canvas[i][j];
}
}
if(i<posD){
cout<<'\n';
}
}
}
模拟光线
这才是本题真正的难点。好吧其实也不难。
发现我们并不会投影到二维平面,因此我们大力模拟。
设当前处理到 \((i,j,k)\)。
显然的是,我们仅仅需要处理顶面、正面、右面即可。
同时,如果给出了不能照射到的条件,那么在一定空间想象能力的基础上理解条件将会是很简单的。
如果没有说明能否照射到,说明该方向始终照射不到那一面。
西北方
top:- 若存在 \(h_{i-l,j-l}\geq k+l\),则
top不能照到。 - 若存在 \(h_{i-l+1,j-l}\geq k+l\),则
top[2]、top[3]不能照到。 - 若存在 \(h_{i-l,j-l+1}\geq k+l\),则
top[1]、top[4]不能照到。
- 若存在 \(h_{i-l,j-l}\geq k+l\),则
正北方
top:若存在 \(h_{i-l,j}\geq k+l\),则top不能照到。
东北方
top:- 若存在 \(h_{i-l,j+l}\geq k+l\),则
top不能照到。 - 若存在 \(h_{i-l,j+l-1}\geq k+l\),则
top[1]、top[2]不能照到。 - 若存在 \(h_{i-l+1,j+l}\geq k+l\),则
top[3]、top[4]不能照到。
- 若存在 \(h_{i-l,j+l}\geq k+l\),则
right:- 若存在 \(h_{i-l,j+l}\geq k+l\),则
right不能照到。 - 若存在 \(h_{i-l+1,j+l}\geq k+l-1\),则
right不能照到。 - 若存在 \(h_{i-l,j+l}\geq k+l-1\),则
right[3]、right[4]不能照到。
- 若存在 \(h_{i-l,j+l}\geq k+l\),则
正西方
top:若存在 \(h_{i,j-l}\geq k+l\),则top不能照到。
竖直方
其实是垂直从上入光。
这个比较特别,不存在遮挡关系,所有 \((i,j,h_{i,j})\) 的 top 都能照到,其余均照不到。
正东方
top:若存在 \(h_{i,j+l}\geq k+l\),则top不能照到。right:若存在 \(h_{i,j+l}\geq k+l-1\),则right不能照到。
西南方
top:- 若存在 \(h_{i+l,j-l}\geq k+l\),则
top不能照到。 - 若存在 \(h_{i+l-1,j-l}\geq k+l\),则
top[1]、top[2]不能照到。 - 若存在 \(h_{i+l,j-l+1}\geq k+l\),则
top[3]、top[4]不能照到。
- 若存在 \(h_{i+l,j-l}\geq k+l\),则
front:- 若存在 \(h_{i+l,j-l}\geq k+l\),则
front不能照到。 - 若存在 \(h_{i+l,j-l+1}\geq k+l-1\),则
front不能照到。 - 若存在 \(h_{i+l,j-l}\geq k+l-1\),则
front[2]、front[3]不能照到。
- 若存在 \(h_{i+l,j-l}\geq k+l\),则
正南方
top:若存在 \(h_{i+l,j}\geq k+l\),则top不能照到。front:若存在 \(h_{i+l,j}\geq k+l-1\),则front不能照到。
东南方
top:- 若存在 \(h_{i+l,j+l}\geq k+l\),则
top不能照到。 - 若存在 \(h_{i+l-1,j+l}\geq k+l\),则
top[1]、top[4]不能照到。 - 若存在 \(h_{i+l,j+l-1}\geq k+l\),则
top[2]、top[3]不能照到。
- 若存在 \(h_{i+l,j+l}\geq k+l\),则
front:- 若存在 \(h_{i+l,j+l}\geq k+l\),则
front不能照到。 - 若存在 \(h_{i+l,j+l-1}\geq k+l-1\),则
front不能照到。 - 若存在 \(h_{i+l,j+l}\geq k+l-1\),则
front[3]、front[4]不能照到。
- 若存在 \(h_{i+l,j+l}\geq k+l\),则
right:- 若存在 \(h_{i+l,j+l}\geq k+l\),则
right不能照到。 - 若存在 \(h_{i+l-1,j+l}\geq k+l-1\),则
right不能照到。 - 若存在 \(h_{i+l,j+l}\geq k+l-1\),则
right不能照到。
- 若存在 \(h_{i+l,j+l}\geq k+l\),则
杂谈
其实写完之后再看这题,也还好。因为可以发现 top、front、right 的光线遮挡大多是重复/相似的。
但是当时第一次模拟赛考这题,我都不敢写;第二次模拟赛考这题,不给大样例,自信写了个 \(\text{15pts}\) 暴力因为不输出 K 爆 \(0\)。
但现在再看这题,如果有耐心和一定空间想象能力,模拟写出来还是比较容易不那么难的。
但我仍然不想再次遇见它。
调试的话肯定需要手搓复杂一点的图并模拟光线,但如果手玩光线玩清楚了那你其实也就会写了……因此如果你空间想象能力很差,那这个题估计要做半天。
同时这个题的部分分也是迷惑,东南方最难,东南方会了其他方向也就会了。
总而言之,还是需要更多的耐心来完成这种题目。
参考代码
//#include<bits/stdc++.h>
#include<algorithm>
#include<iostream>
#include<cstring>
#include<iomanip>
#include<cstdio>
#include<string>
#include<vector>
#include<cmath>
#include<ctime>
#include<deque>
#include<queue>
#include<stack>
#include<list>
using namespace std;
constexpr const char shape[13][14]={
"####+-------+",
"###/2\\1111'/|",
"##/22.*'44/9|",
"#/.3333\\4/9/|",
"+-------+9.C|",
"|\\55555/|\\:C|",
"|6\\555/8|A*C|",
"|66\\5/88|A:\\|",
"|666X888|A'B+",
"|66/7\\88|/B/#",
"|6/777\\8|B/##",
"|/77777\\|/###",
"+-------+####"
};
constexpr const int N=100,M=100,H=100;
int n,m,h[N+1][M+1];
constexpr const char colorList[8]={'K','B','G','C','R','P','Y','W'};
struct color{
int rgb;
color(){
rgb=0;
}
color(int x){
rgb=x;
}
color(char ch){
rgb=0;
for(int i=0;i<8;i++){
if(ch==colorList[i]){
rgb=i;
break;
}
}
}
char ch(){
return colorList[rgb];
}
};
color operator |(color a,color b){
return a.rgb|b.rgb;
}
color operator |=(color &a,color b){
return a=a|b;
}
struct cube{
color top[5],front[5],right[5];
char query(char ch){
if('1'<=ch&&ch<='4'){
return top[ch-'0'].ch();
}else if('5'<=ch&&ch<='8'){
return front[ch-'4'].ch();
}else if(ch=='9'){
return right[1].ch();
}else if('A'<=ch&&ch<='C'){
return right[ch-'A'+2].ch();
}else{
return ch;
}
}
}a[N+1][M+1][H+1];
void Paint(){
char c;
//西北方
cin>>c;
if(c!='*'){
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
int &k=h[i][j];
bool top[5]={true,true,true,true,true};
for(int l=1;1<=i-l&&1<=j-l;l++){
if(h[i-l][j-l]>=k+l){
top[1]=top[2]=top[3]=top[4]=false;
break;
}
}
for(int l=1;1<=i-l+1&&1<=j-l;l++){
if(h[i-l+1][j-l]>=k+l){
top[2]=top[3]=false;
break;
}
}
for(int l=1;1<=i-l&&1<=j-l+1;l++){
if(h[i-l][j-l+1]>=k+l){
top[1]=top[4]=false;
break;
}
}
for(int l=1;l<=4;l++){
if(top[l]){
a[i][j][k].top[l]|=c;
}
}
}
}
}
//正北方
cin>>c;
if(c!='*'){
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
int k=h[i][j];
bool top=true;
for(int l=1;1<=i-l;l++){
if(h[i-l][j]>=k+l){
top=false;
break;
}
}
if(top){
for(int l=1;l<=4;l++){
a[i][j][k].top[l]|=c;
}
}
}
}
}
//东北方
cin>>c;
if(c!='*'){
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
for(int k=1;k<=h[i][j];k++){
bool top[5]={true,true,true,true,true};
for(int l=1;1<=i-l&&j+l<=m;l++){
if(h[i-l][j+l]>=k+l){
top[1]=top[2]=top[3]=top[4]=false;
break;
}
}
for(int l=1;1<=i-l&&j+l-1<=m;l++){
if(h[i-l][j+l-1]>=k+l){
top[1]=top[2]=false;
break;
}
}
for(int l=1;1<=i-l+1&&j+l<=m;l++){
if(h[i-l+1][j+l]>=k+l){
top[3]=top[4]=false;
break;
}
}
for(int l=1;l<=4;l++){
if(top[l]){
a[i][j][k].top[l]|=c;
}
}
bool right[5]={true,true,true,true,true};
for(int l=1;1<=i-l&&j+l<=m;l++){
if(h[i-l][j+l]>=k+l){
right[1]=right[2]=right[3]=right[4]=false;
break;
}
}
for(int l=1;1<=i-l+1&&j+l<=m;l++){
if(h[i-l+1][j+l]>=k+l-1){
right[1]=right[2]=right[3]=right[4]=false;
break;
}
}
for(int l=1;1<=i-l&&j+l<=m;l++){
if(h[i-l][j+l]>=k+l-1){
right[3]=right[4]=false;
break;
}
}
for(int l=1;l<=4;l++){
if(right[l]){
a[i][j][k].right[l]|=c;
}
}
}
}
}
}
//正西方
cin>>c;
if(c!='*'){
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
int k=h[i][j];
bool top=true;
for(int l=1;1<=j-l;l++){
if(h[i][j-l]>=k+l){
top=false;
break;
}
}
if(top){
for(int l=1;l<=4;l++){
a[i][j][k].top[l]|=c;
}
}
}
}
}
//垂直从上入光
cin>>c;
if(c!='*'){
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
int &k=h[i][j];
for(int l=1;l<=4;l++){
a[i][j][k].top[l]|=c;
}
}
}
}
//正东方
cin>>c;
if(c!='*'){
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
for(int k=1;k<=h[i][j];k++){
bool top=true;
bool right=true;
for(int l=1;j+l<=m;l++){
if(h[i][j+l]>=k+l){
top=false;
break;
}
}
if(top){
for(int l=1;l<=4;l++){
a[i][j][k].top[l]|=c;
}
}
for(int l=1;j+l<=m;l++){
if(h[i][j+l]>=k+l-1){
right=false;
break;
}
}
if(right){
for(int l=1;l<=4;l++){
a[i][j][k].right[l]|=c;
}
}
}
}
}
}
//西南方
cin>>c;
if(c!='*'){
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
for(int k=1;k<=h[i][j];k++){
bool top[5]={true,true,true,true,true};
for(int l=1;i+l<=n&&1<=j-l;l++){
if(h[i+l][j-l]>=k+l){
top[1]=top[2]=top[3]=top[4]=false;
break;
}
}
for(int l=1;i+l-1<=n&&1<=j-l;l++){
if(h[i+l-1][j-l]>=k+l){
top[1]=top[2]=false;
break;
}
}
for(int l=1;i+l<=n&&1<=j-l+1;l++){
if(h[i+l][j-l+1]>=k+l){
top[3]=top[4]=false;
break;
}
}
for(int l=1;l<=4;l++){
if(top[l]){
a[i][j][k].top[l]|=c;
}
}
bool front[5]={true,true,true,true,true};
for(int l=1;i+l<=n&&1<=j-l;l++){
if(h[i+l][j-l]>=k+l){
front[1]=front[2]=front[3]=front[4]=false;
break;
}
}
for(int l=1;i+l<=n&&1<=j-l+1;l++){
if(h[i+l][j-l+1]>=k+l-1){
front[1]=front[2]=front[3]=front[4]=false;
break;
}
}
for(int l=1;i+l<=n&&1<=j-l;l++){
if(h[i+l][j-l]>=k+l-1){
front[2]=front[3]=false;
break;
}
}
for(int l=1;l<=4;l++){
if(front[l]){
a[i][j][k].front[l]|=c;
}
}
}
}
}
}
//正南方
cin>>c;
if(c!='*'){
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
for(int k=1;k<=h[i][j];k++){
bool top=true;
bool front=true;
for(int l=1;i+l<=n;l++){
if(h[i+l][j]>=k+l){
top=false;
break;
}
}
if(top){
for(int l=1;l<=4;l++){
a[i][j][k].top[l]|=c;
}
}
for(int l=1;i+l<=n;l++){
if(h[i+l][j]>=k+l-1){
front=false;
break;
}
}
if(front){
for(int l=1;l<=4;l++){
a[i][j][k].front[l]|=c;
}
}
}
}
}
}
//东南方
cin>>c;
if(c!='*'){
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
for(int k=1;k<=h[i][j];k++){
bool top[5]={true,true,true,true,true};
for(int l=1;i+l<=n&&j+l<=m;l++){
if(h[i+l][j+l]>=k+l){
top[1]=top[2]=top[3]=top[4]=false;
break;
}
}
for(int l=1;i+l-1<=n&&j+l<=m;l++){
if(h[i+l-1][j+l]>=k+l){
top[1]=top[4]=false;
break;
}
}
for(int l=1;i+l<=n&&j+l-1<=m;l++){
if(h[i+l][j+l-1]>=k+l){
top[2]=top[3]=false;
break;
}
}
for(int l=1;l<=4;l++){
if(top[l]){
a[i][j][k].top[l]|=c;
}
}
bool front[5]={true,true,true,true,true};
for(int l=1;i+l<=n&&j+l<=m;l++){
if(h[i+l][j+l]>=k+l){
front[1]=front[2]=front[3]=front[4]=false;
}
}
for(int l=1;i+l<=n&&j+l-1<=m;l++){
if(h[i+l][j+l-1]>=k+l-1){
front[1]=front[2]=front[3]=front[4]=false;
break;
}
}
for(int l=1;i+l<=n&&j+l<=m;l++){
if(h[i+l][j+l]>=k+l-1){
front[3]=front[4]=false;
}
}
for(int l=1;l<=4;l++){
if(front[l]){
a[i][j][k].front[l]|=c;
}
}
bool right[5]={true,true,true,true,true};
for(int l=1;i+l<=n&&j+l<=m;l++){
if(h[i+l][j+l]>=k+l){
right[1]=right[2]=right[3]=right[4]=false;
break;
}
}
for(int l=1;i+l-1<=n&&j+l<=m;l++){
if(h[i+l-1][j+l]>=k+l-1){
right[1]=right[2]=right[3]=right[4]=false;
break;
}
}
for(int l=1;i+l<=n&&j+l<=m;l++){
if(h[i+l][j+l]>=k+l-1){
right[2]=right[3]=false;
break;
}
}
for(int l=1;l<=4;l++){
if(right[l]){
a[i][j][k].right[l]|=c;
}
}
}
}
}
}
}
constexpr const int row=1e4,col=1e4;
static char canvas[row+1][col+1];
int posL=col+1,posR=-1,posU=row+1,posD=-1;
void place(int i,int j,cube x){
i+=1000,j+=1000;
for(int pi=0;pi<13;pi++){
for(int pj=0;pj<13;pj++){
if(shape[pi][pj]=='#'){
continue;
}
canvas[i+pi][j+pj]=x.query(shape[pi][pj]);
posL=min(posL,j+pj);
posR=max(posR,j+pj);
posU=min(posU,i+pi);
posD=max(posD,i+pi);
}
}
}
void Print(){
for(int i=1,posI=1;i<=n;i++,posI+=4){
for(int j=1,posJ=(n-i)*4+1;j<=m;j++,posJ+=8){
for(int k=1;k<=h[i][j];k++){
place(posI-8*(k-1),posJ,a[i][j][k]);
}
}
}
for(int i=posU;i<=posD;i++){
int r=posR;
while(!canvas[i][r]){
r--;
}
for(int j=posL;j<=r;j++){
if(canvas[i][j]==0){
cout<<' ';
}else{
cout<<canvas[i][j];
}
}
if(i<posD){
cout<<'\n';
}
}
}
int main(){
/*freopen("test.in","r",stdin);
freopen("test.out","w",stdout);*/
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
cin>>n>>m;
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
cin>>h[i][j];
}
}
Paint();
Print();
cout.flush();
/*fclose(stdin);
fclose(stdout);*/
return 0;
}

浙公网安备 33010602011771号