抱歉,您的浏览器无法访问本站

本页面需要浏览器支持(启用)JavaScript


了解详情 >

题面

金先生有一个女朋友没名字。她勤劳勇敢、智慧善良。金先生很喜欢她。为此,金先生用 a3a^31×1×11 \times 1 \times 1的独特的水晶制作了一个边长为 aa 的水晶立方体,他要将这个水晶立方体送给他见过最单纯善良的她。

由于水晶立方体太太,不好运送,金先生还是将它拆开来送出。他相信拼好这个水晶立方难不倒聪明的她。

没名字收到了礼物后果然不一会儿就根据说明将水晶立方体拼好了。没名字发现,有 nn 块水晶在漆黑安静的夜晚会随机以等概率向上下左右前后六个方向的一个发出光。被光照到的水晶显得格外好看。没名字给每一块不会发光的水晶定义了一个好看程度。水晶立方体在夜晚中的好看程度就是每块被光照到的水晶的好看程度之和。没名字想知道,水晶立方体在夜晚中的好看程度的最小值和最大值。

输入格式

第一行是 aa,表示水晶立方体的边长。

接下来 a3a^3 行,每行若干整数。

第一个数 gig_i 表示第 ii 块水晶的好看程度。如果 gi=0g_i=0,代表这块水晶会发光。接下来 363\sim 6个整数,代表与这块水晶有共同面的水晶编号。

输出格式

两个整数,代表水晶立方体在夜晚好看程度的最小值与最大值。

样例

样例输入

1
2
3
4
5
6
7
8
9
2
0 7 2 3
0 8 1 4
4 5 4 1
8 6 3 2
16 3 6 7
32 4 5 8
1 1 8 5
2 2 7 6

样例输出

1
0 12

数据范围与提示

对于所有数据,1<a70,gi<1000000,n81<a\le 70, g_i<1000000, n\le 8

题解

首先,介绍一下 c++ stl 里的神物——stringstream

这东西能像 cin 那样读入,但是是从字符串中读入,所以我们就不用打快读了(虽然慢了一点,但开氧气之后海星)。

头文件<sstream>

大概就是这样读:

1
2
3
4
5
6
7
8
9
for(int i = 1; i <= n; ++i)
{
getline(cin, tmp);
stringstream ss(tmp);
ss >> dep[i];
int aa;
while(ss >> aa)
add_edge(i, aa);
}

题目中的照射不仅只照到了与该水晶相邻的水晶,它把整条射线上的水晶全照到了。

所以我们只能考虑建出这个立方体后重新建图。

我的思路是这样的:

先找到一个度为 3 的点作为一个角的点,用 bfs 求出其道个点的最短路。记以该点为原点的第 ii 个点坐标为(x1,i,y1,i,z1,i)(x_{1, i}, y_{1, i}, z_{1, i}),最短路为dist[0][i]dist[0][i],显然有dist[0][i]=x1,i1+y1,i1+z1,i1dist[0][i] = x_{1, i} - 1+ y_{1, i} - 1 + z_{1, i} - 1

我们任选一个平面,要求包含刚才那个点。我们找到在该平面上该点对角线上的点,其实就是随便找一个度为 3(在角上)且与原点距离2(n1)2(n-1)(在该平面上最远)的点。以该点为原点跑一遍最短路,记dist[1][i]dist[1][i]

由于我们设计最短路时,可以先走到该点正下方,再往上爬,同样,我们可以设计成先走yy,再走xx,最后走zz

我们发现 dist[0][i]+dist[1][i]=2(n1)dist[0][i] +dist[1][i]=2(n-1),我们把它减去,就只剩下2z2z 了,于是 zz 就求出来了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/**
ddn 是度数
di 是原点
poi 记录点的 x, y, z
这里的 n 已经是点的个数了
tn 才是边长
由于从 1 开始,所以有些地方微调了一下
*/
for(di[0] = 1; ddn[di[0]] > 3; ++di[0]);
bfs(0);
for(int i = 1; i <= n; ++i)
{
if(ddn[i] == 3 && dist[0][i] == ((tn-1)<<1))
{
di[1] = i;
break;
}
}
bfs(1);
for(int i = 1; i <= n; ++i)
poi[i].z = (dist[0][i] + dist[1][i] - ((tn-2)<<1)) >> 1;

我们可以用同样的方法把 xx 解出来,最后的 yy 只要减一下就可以了。因为我的坐标是从 1 开始的,所以我对坐标做了一些微调,从 0 开始就没有这个问题。

由于之前 bfs 的数据我们还能用,所以我们只需再一遍 bfs 即可

1
2
3
4
5
6
7
8
9
10
11
12
for(int i = 1; i <= n; ++i)
if(poi[i].z == tn && dist[0][i] == dist[1][i] && ddn[i] == 3)
di[2] = i;
bfs(2);
for(int i = 1; i <= n; ++i)
{
poi[i].x = (dist[0][i] + dist[2][i] - ((tn-1)<<1)) >> 1;
poi[i].y = (dist[0][i] - poi[i].x - poi[i].z) + 1;
poi[i].x++;
poi[i].y++;
mmap[poi[i].x][poi[i].y][poi[i].z] = i;
}

于是我们就建好图了。

后面的大力 dfs 也没什么好说的。

下面把 ac 代码贴一下,由于 stringstream 在没有氧气的情况下极慢,所以必须开氧气。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
#include <cstring>
#include <iostream>
#include <fstream>
#include <sstream>//stringstream
#include <queue>

using namespace std;

const int maxm = 2058000;// 这里的最大边数我是把点数加起来乘了 6
const int maxn = 75*75*75;
const int inf = 0x3f3f3f3f;

int n;// 总个数
int tn;// 边长

struct Edge
{
int to, nxt;
} e[maxm<<1];

int first[maxn];

int cnt;
inline void add_edge(int f, int t)
{
e[++cnt].nxt = first[f];
first[f] = cnt;
e[cnt].to = t;
}

int dirx[] = {1, -1, 0, 0, 0, 0};
int diry[] = {0, 0, 1, -1, 0, 0};
int dirz[] = {0, 0, 0, 0, 1, -1};

int dep[maxn];//" 好看程度 "
int vis[maxn];
int minn = inf;
int maxx = -inf;
int ll[10];// 会发光的水晶的编号
int ddn[maxn];// 度数
int zl;// 这是个 ddn 的 cnt
int mmap[73][73][73];

struct zb
{
int x, y, z;
} poi[maxn];

#define pan (x > 0 && x <= tn && y > 0 && y <= tn && z > 0 && z <= tn)
#define nxxt x += dirx[i], y += diry[i], z += dirz[i]

inline int getans(int i, zb a)// 能加多少 " 好看程度 "
{
int ans = 0;
int x = a.x, y = a.y, z = a.z;
for(; pan; nxxt)
if(!vis[mmap[x][y][z]]++)// 由于待会儿回溯时要删除,所以懒到家的我就直接用 ++,-- 代替记录了
ans += dep[mmap[x][y][z]];
return ans;
}

inline void delvis(int i, zb a)// 回溯时把 dfs 前加的 vis 删除
{
int x = a.x, y = a.y, z = a.z;
for(; pan; nxxt)
vis[mmap[x][y][z]]--;
}

inline void dfs(int now, int ans)// 大力枚举所有情况
{
if(now > zl)
{
minn = min(minn, ans);
maxx = max(maxx, ans);
return;
}
for(int i = 0; i < 6; ++i)
{
dfs(now+1, ans + getans(i, poi[ll[now]]));
delvis(i, poi[ll[now]]);
}
}

int dist[4][maxn];
int di[4];
bool viss[maxn];

inline void bfs(int id)// 求最短路
{
memset(viss, 0, sizeof(viss));
queue<int> q;
int from = di[id];
viss[from] = true;
q.push(from);
while(!q.empty())
{
int now = q.front();
q.pop();
for(int i = first[now]; i; i = e[i].nxt)
{
int to = e[i].to;
if(!viss[to])
{
viss[to] = true;
dist[id][to] = dist[id][now] + 1;
q.push(to);
}
}
}
}

int main()
{
ios:: sync_with_stdio(false);
cin >> n;
tn = n;
string tmp;
getline(cin, tmp);
n *= n * n;
for(int i = 1; i <= n; ++i)
{
getline(cin, tmp);
stringstream ss(tmp);
ss >> dep[i];
int aa;
if(!dep[i])
{
vis[i] = true;
ll[++zl] = i;
}
while(ss >> aa)
{
add_edge(i, aa);
ddn[i]++;
}
}
for(di[0] = 1; ddn[di[0]] > 3; ++di[0]);
bfs(0);
for(int i = 1; i <= n; ++i)
{
if(ddn[i] == 3 && dist[0][i] == ((tn-1)<<1))
{
di[1] = i;
break;
}
}
bfs(1);
for(int i = 1; i <= n; ++i)// 得到 z
{
poi[i].z = (dist[0][i] + dist[1][i] - ((tn-2)<<1)) >> 1;
if(poi[i].z == tn && dist[0][i] == dist[1][i] && ddn[i] == 3)
di[2] = i;
}
bfs(2);
for(int i = 1; i <= n; ++i)// 得到 x, y
{
poi[i].x = (dist[0][i] + dist[2][i] - ((tn-1)<<1)) >> 1;
poi[i].y = (dist[0][i] - poi[i].x - poi[i].z) + 1;
poi[i].x++;
poi[i].y++;
mmap[poi[i].x][poi[i].y][poi[i].z] = i;
}
dfs(1, 0);
cout << minn << ' ' << maxx << endl;
fclose(stdin);
fclose(stdout);
return 0;
}

评论