UVa 10381 - The Rock

Problem

給定一個 N x M 地圖,已知有部分的牆壁和一個透明的牆壁,每一個障礙物都佔據一個方格,而透明牆壁一開始不知道位於何處,直到碰壁的時候才發現,找一條最慘情況下的最短路徑。

Sample Input

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
8 8
..X.....
........
##.##...
........
##.##...
##.##...
........
..E.....
3 4
..X.
.##.
.E..

Sample Output

1
2
15
11

Solution

最小化最大值,首先窮舉任何一個透明牆壁可能出現的地方,對於透明牆壁的鄰近四格建立再碰壁方向下,抵達終點的最短路徑。

接著,使用最短路的方式進行更新,在這裡使用 dijkstra 的方式,對於每一個點的狀態為 (走到這裡第幾步) = 從起點走到這,最慘的偏移路徑最小化 為何。

概念上理解,設計一條路徑,路徑畫好後,對於每一點賭下一個點就是透明牆壁,偏移路徑長最長為何就會是該路徑的花費。

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
#include <stdio.h>
#include <string.h>
#include <queue>
#include <assert.h>
#include <algorithm>
using namespace std;
char g[64][64];
int dist[64][64], walldist[64][64][4];
int n, m;
const int dx[] = {0, 1, 0, -1};
const int dy[] = {1, 0, -1, 0};
struct State {
int x, y;
int g, h;
State(int a=0, int b=0, int c=0, int d=0):
x(a), y(b), g(c), h(d) {}
bool operator<(const State &a) const {
return h > a.h;
}
};
int dp[40][40][40 * 40];
int search(int sx, int sy, int ex, int ey) {
priority_queue<State> pQ;
State u, v;
int tx, ty, h;
pQ.push(State(sx, sy, 0, 0));
memset(dp, 63, sizeof(dp));
while (!pQ.empty()) {
u = pQ.top(), pQ.pop();
if (u.h > dp[u.x][u.y][u.g])
continue;
if (u.x == ex && u.y == ey)
return u.h;
for (int i = 0; i < 4; i++) {
tx = u.x + dx[i], ty = u.y + dy[i];
if (tx < 0 || ty < 0 || tx >= n || ty >= m)
continue;
if (g[tx][ty] == '#' || g[tx][ty] == 'E')
continue;
if (g[tx][ty] == '.')
h = max(u.h, u.g + walldist[u.x][u.y][i]);
else if (g[tx][ty] == 'X')
h = max(u.h, u.g);
assert(u.g + 1 < 40 * 40);
if (dp[tx][ty][u.g + 1] > h) {
dp[tx][ty][u.g + 1] = h;
pQ.push(State(tx, ty, u.g + 1, h));
}
}
}
return 0;
}
void bfs(int sx, int sy) {
memset(dist, 63, sizeof(dist));
queue<int> X, Y;
int tx, ty;
X.push(sx), Y.push(sy);
dist[sx][sy] = 0;
while (!X.empty()) {
sx = X.front(), X.pop();
sy = Y.front(), Y.pop();
for (int i = 0; i < 4; i++) {
tx = sx + dx[i], ty = sy + dy[i];
if (tx < 0 || ty < 0 || tx >= n || ty >= m)
continue;
if (g[tx][ty] == '#' || dist[tx][ty] <= dist[sx][sy] + 1)
continue;
dist[tx][ty] = dist[sx][sy] + 1;
X.push(tx), Y.push(ty);
}
}
}
int main() {
int testcase;
scanf("%d", &testcase);
while (testcase--) {
scanf("%d %d", &n, &m);
for (int i = 0; i < n; i++)
scanf("%s", g[i]);
int sx, sy, ex, ey, tx, ty;
memset(walldist, 0, sizeof(walldist));
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
if (g[i][j] == 'E') sx = i, sy = j;
if (g[i][j] == 'X') ex = i, ey = j;
}
}
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
if (g[i][j] != '.')
continue;
g[i][j] = '#';
bfs(ex, ey);
for (int k = 0; k < 4; k++) {
tx = i + dx[k], ty = j + dy[k];
if (tx < 0 || ty < 0 || tx >= n || ty >= m)
continue;
if (g[tx][ty] == '#')
continue;
walldist[tx][ty][(k+2)%4] = dist[tx][ty];
}
g[i][j] = '.';
}
}
int ret = search(sx, sy, ex, ey);
printf("%d\n", ret);
}
return 0;
}
/*
2
8 8
..X.....
........
##.##...
........
##.##...
##.##...
........
..E.....
3 4
..X.
.##.
.E..
*/
Read More +

自然語言處理 - HW02

編譯環境

Dev-C++ 5.6.3

作業內容

給予 5000 筆的亞馬遜書店評論,每一個評論都已經分類好,總共有五種類型的書評,分別為 book, dvd, health, music, 以及 toys_games

利用這 5000 筆分類好的書評寫一個分類器,接著讀入另外測試資料,測試資料每一個書評有既定分類,使用分類器判斷是否符合既定分類,分別計算對於每一種分類書評的準確度 (Accuracy) 和精準度 (Precision)。

  • 準確度:接近正確答案的機率,在這裡可以理解為分類出來的結果等於實際結果的機率。
  • 精準度:測試結果一致的機率,在這裡可以理解為分類出來的結果中,實際上是正確的機率。(是否集中於某個實際結果)

還有一個混用準確度和精準度的概念 f,假設準確度為 R,精準度為 P。

$F = \frac{1}{\alpha \frac{1}{P} + (1 - \alpha) \frac{1}{R}} = \frac{(\beta^{2} + 1) PR}{\beta^{2}P+R}$

所謂的 F1 定義為$\beta = 1, \alpha = 1/2$,最後得到$F = 2PR / (P+R)$

關於輸入輸出資料

  • gold_standard.xml : 黃金標準的 5000 筆評論
  • test_outcome.xml :原本認為是 測試用的反饋資料,用來檢測寫的分類器好不好。 後更正為助教的分類器跑出來的結果。

通常會拿到一大筆資料,經驗上 3/4 的資料會拿來訓練,剩下 1/4 會拿來檢測。也就是資料量於 3 : 1,至於一段資料怎麼切分有很多方式,可以擷取中間一段的 1/4 … 等都可以。

看一下 xml 格式

gold_standard.xml
1
2
3
4
5
6
7
8
<RDF rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
<Text category="music">Forget the fact that despite the grumbling
about today's "music" ...</Text>
<Text category="health">Printed all over the box are the words
"with infrared heat". ...</Text>
<Text category="music">I just finished seeing the movie and loved
the song at the end ...</Text>
...

看起來最多兩層,如果不用插件來解析 XML,遞迴處理寫一個 parser 應該不是問題。

於是就這麼蠻幹 XML parser 下去,仔細查閱一下給定的 XML,會發現文件中有 &amp;amp;&amp;amp;quot; 的確在 XML 的元素 content 不會出現 >, < … 等字元,就跟 HTML 的 character entities 一樣都需要被替換成特殊的顯示規格。但真沒有見過上述的那兩個格式,仔細檢查發現應該是對應 and 字串和 " 引號。解析完作 replace 動作。

回過頭來看這次所需要的公式:

$$P(c) = \frac{N_{c}}{N} \\ P(w|c) = \frac{count(w,c)+1}{count(c)+|V|} \\ P(c|s) = P(c) \prod P(w_{i}|c)$$

參數說明:

$P(c)$ 表示該分類佔有群體的機率,也就是在 5000 筆評論中,分類 c 佔有百分比$N_{c}$ 表示有多少筆評論屬於 c 分類$N$ 表示評論筆數,這裡已知$N = 5000$ $P(w|c)$ 單詞 w 在分類 c 的出現機率為何$count(w,c)$ 單詞 w 在分類 c 中出現的次數$count(c)$ 屬於 c 分類中字詞總數 (評論總共有多少字)$|V|$ 分類 c 中使用的單詞集合大小。
*$P(c|s)$ 評論句子 s 在分類 c 的機率為何。

隨後找$P(c|s)$ 機率值最大作為分類依準。

最後要找到分類是否正確,對於每一種分類 c 會得到表格

實際結果\分類結果 Classifier no Classifier yes
Truth no true negative(TN) false positive(FP)
Truth yes false negative(FN) true positive(TP)

準確度 R 意即:TP / (TP + FN)、精準度 P 意即:TP / (TP + FP)

對於 Micro-average 和 Macro-average 的計算,Micro 和 Macro 概念上我理解程式幾何平均和算數平均的概念,Micro 會把每個分類 c 的表單結果累加到一個表格,隨後按照一般分類算法計算準確和精準度,而 Macro 則是將每個分類算完的結果取算術平均。

代碼

傻傻地用分類器判斷與助教的決策比較的運行結果。

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
process gold_standard.xml ...
statistics model built ...
--------------------+----------
book | 1000
dvd | 1000
health | 1000
music | 1000
toys_games | 1000
--------------------+----------
process test_outcome.xml ...
testing test_outcome.xml ...
| AC\Assign| book| dvd| health| music|toys_games|
| book| 892| 29| 17| 9| 25|
| dvd| 22| 829| 16| 48| 41|
| health| 30| 19| 975| 46| 177|
| music| 5| 13| 12| 870| 28|
|toys_games| 10| 16| 43| 21| 807|
book
p :0.9301355579
r :0.9176954733
f :0.9238736406
dvd
p :0.9150110375
r :0.8671548117
f :0.8904403867
health
p :0.9172154280
r :0.7818765036
f :0.8441558442
music
p :0.8752515091
r :0.9375000000
f :0.9053069719
toys_games
p :0.7486085343
r :0.8996655518
f :0.8172151899
Micro-average
p :0.8746000000
r :0.8746000000
f :0.8746000000
Macro-average
p :0.8772444134
r :0.8807784681
f :0.8790078886
--------------------------------
Process exited after 6.545 seconds with return value 0

後來發現是公式理解錯誤

  • 真陽性 (TP, true positive)
    正確的肯定。又稱:命中 (hit)
  • 真陰性 (TN, true negative)
    正確的否定。又稱:正確拒絕 (correct rejection)
  • 偽陽性 (FP, false positive)
    錯誤的肯定,又稱:假警報 (false alarm),第一型錯誤
  • 偽陰性 (FN, false negative)
    錯誤的否定,又稱:未命中 (miss),第二型錯誤

準確度就是在正確答案中找誤判,而精準度就是在判斷正確下找錯誤。

後來實驗用 gold_standard.xml train 和 test,運行結果為

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
| AC\Assign| book| dvd| health| music|toys_games|
| book| 950| 7| 15| 3| 25|
| dvd| 7| 895| 33| 21| 44|
| health| 1| 0| 984| 1| 14|
| music| 1| 3| 15| 968| 13|
|toys_games| 0| 1| 16| 1| 982|
book
p :0.9906152242
r :0.9500000000
f :0.9698825932
dvd
p :0.9878587196
r :0.8950000000
f :0.9391395593
health
p :0.9256820320
r :0.9840000000
f :0.9539505574
music
p :0.9738430584
r :0.9680000000
f :0.9709127382
toys_games
p :0.9109461967
r :0.9820000000
f :0.9451395573
Micro-average
p :0.9558000000
r :0.9558000000
f :0.9558000000
Macro-average
p :0.9577890462
r :0.9558000000
f :0.9567934893

當然不能拿相同的 train 和 test data 來用,但是也明顯地發現,這個 model 仍然沒有辦法達到很高純度的結果,在部分比例中也只達到 90% 的精度。

那有沒有一種情況,我們將六種分類的共同交集字符給去除?這樣的效果會不會比較好呢?例如常用的 Ithinkit … 等,在 model 中直接無視這些常用字集的效果如何呢?

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
void custom() {
set<string> common;
int first_set = 1;
for (map<string, map<string, int> >::iterator it = categories.begin();
it != categories.end(); it++) {
map<string, int> &R = it->second;
set<string> s;
for (map<string, int>::iterator jt = R.begin();
jt != R.end(); jt++)
s.insert(jt->first);
if (first_set) common = s, first_set = 0;
else {
set<string> C;
set_intersection(common.begin(), common.end(), s.begin(), s.end(), inserter(C, C.begin()));
common = C;
}
}
for (map<string, map<string, int> >::iterator it = categories.begin();
it != categories.end(); it++) {
map<string, int> &R = it->second;
for (set<string>::iterator jt = common.begin();
jt != common.end(); jt++)
R.erase(*jt);
int cnt = 0;
for (map<string, int>::iterator jt = R.begin();
jt != R.end(); jt++)
cnt += jt->second;
count_category[it->first] = cnt;
}
}

上述代碼為將每一個分類的字集與共同交集,並且將共同交集從每個分類中移除,同時將記數重新統計。根據實驗結果,很多分類都直接噴掉,也許是專有名詞使用的量太少,雖然每個字集仍然有上千上萬不等,但是在分類效應上某些分類完全消失。效果差到不行,所以就直接無視吧。或者提供點意見吧。

回過頭來解一下關於作業內容,其實作業內容可以化簡為:

輸入只有一組測資,該組測資會有數行,每一行上會有兩個分類結果,第一個分類結果為標準答案,第二個分類結果為根據模型運算的結果。對於測資輸出每一個分類的準確度、精準度以及 f1 的數據結果。

兩個輸入檔案可以壓縮為

Input

1
2
3
4
5
6
7
5000
music music
book health
music music
toys_games book
music music
... here are more rows

很明顯地,第二個評論中,誤把 book 類型判斷成 health。

Output

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
| AC\Assign| book| dvd| health| music|toys_games|
| book| 907| 25| 48| 7| 13|
| dvd| 35| 873| 43| 24| 25|
| health| 10| 2| 932| 11| 45|
| music| 9| 46| 56| 865| 24|
|toys_games| 11| 10| 168| 21| 790|
book
p :0.9331275720
r :0.9070000000
f :0.9198782961
dvd
p :0.9131799163
r :0.8730000000
f :0.8926380368
health
p :0.7473937450
r :0.9320000000
f :0.8295505118
music
p :0.9321120690
r :0.8650000000
f :0.8973029046
toys_games
p :0.8807134894
r :0.7900000000
f :0.8328940432
Micro-average
p :0.8734000000
r :0.8734000000
f :0.8734000000
Macro-average
p :0.8813053583
r :0.8734000000
f :0.8773348714
--------------------------------
Process exited after 1.736 seconds with return value 0

答案算出來當然跟助教一下啊,知道什麼是 sample output 嗎?在 ACMer 的眼中,代表根據算法不同,答案也許會有誤差,或者是測資不同造成答案不同。而 sample output 通常告訴我們的是輸出格式與可能情況,而非測資的正確答案。

我就是很笨,無法理解這個世界。不知道那檔案是有序對應,看到 attribute 名稱 name 一樣,內容 content 不一樣就自動補完無法理解的英文部分,而沒有去看 tag 中 inner content 內容是相同的。

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
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
#include <stdio.h>
#include <map>
#include <vector>
#include <iostream>
#include <fstream>
#include <assert.h>
#include <sstream>
#include <math.h>
#include <ctype.h>
#include <string>
#include <string.h>
#include <set>
#include <algorithm>
using namespace std;
class XMLParser {
public:
struct Node {
string tag_name, content;
map<string, string> attr;
vector<Node> child;
};
Node root;
XMLParser(string text = "") {
sin << text;
root = Node();
build(root, "");
}
void replaceAll(std::string& str, const std::string& from, const std::string& to) {
if(from.empty())
return;
size_t start_pos = 0;
while((start_pos = str.find(from, start_pos)) != std::string::npos) {
str.replace(start_pos, from.length(), to);
start_pos += to.length(); // In case 'to' contains 'from', like replacing 'x' with 'yx'
}
}
string html2text(string s) {
string ret(s);
replaceAll(ret, "&amp;amp;quot;", "\"");
replaceAll(ret, "&amp;amp;", "and");
return ret;
}
private:
stringstream sin;
void getTagContent(Node &u) {
char c, div;
while (sin.get(c)) {
if (c == '<') {
while (sin.get(c) && isspace(c));
u.tag_name = c;
while (sin.get(c) && c != '>' && !isspace(c))
u.tag_name += c;
if (c == '>') return;
while (true) {
while (sin.get(c) && isspace(c));
if (c == '>') return;
string a = "", b = "";
a += c;
while (sin.get(c) && !isspace(c) && c != '=')
a += c;
while (sin.get(c) && (isspace(c) || c == '"' || c == '\'')) div = c;
b += c;
while (sin.get(c) && !isspace(c) && c != div)
b += c;
u.attr[a] = b;
}
return;
}
}
}
int build(Node &u, string last) {
getTagContent(u);
if (u.tag_name == last)
return 0;
while (sin.peek() != EOF && sin.peek() != '<') {
char c;
sin.get(c);
u.content += c;
}
u.content = html2text(u.content);
while (sin.peek() != EOF) {
while (sin.peek() != EOF && isspace(sin.peek()))
sin.get();
if (sin.peek() == '<') {
u.child.push_back(Node());
if (!build(u.child[u.child.size() - 1], "/" + u.tag_name)) {
u.child.pop_back();
break;
}
}
}
return 1;
}
};
class StatsModel {
public:
map<string, map<string, int> > categories; // count(w, c)
map<string, int > commentCount; // N_{c}
map<string, int > count_category; // count(c)
map<string, map<string, int> > judgeTable;
int N, V;
int microtable[2][2]; // [truth 0:1][classifier 0:1]
StatsModel() {
N = V = 0;
memset(microtable, 0, sizeof(microtable));
}
vector<string> normalSentence(string content) {
vector<string> w;
stringstream sin(content);
string token;
while (sin >> token) {
token = igntrim(token);
token = str2lower(token);
if (token.length() > 0)
w.push_back(token);
}
return w;
}
void recordJudge(string truth, string classifier) {
judgeTable[truth][classifier]++;
for (map<string, int>::iterator it = commentCount.begin();
it != commentCount.end(); it++) {
int t = (it->first == truth);
int c = (it->first == classifier);
microtable[t][c]++;
}
}
string judgeComment(string category, string content) {
vector<string> w = normalSentence(content);
double Pc, P;
double maxPwc = - 1e+30;
string chooseClass = "";
for (map<string, int>::iterator it = commentCount.begin();
it != commentCount.end(); it++) {
Pc = (double)it->second / N;
P = log(Pc);
map<string, int> &R = categories[it->first];
int count_c = count_category[it->first], count_w_c;
for (int i = 0; i < w.size(); i++) {
count_w_c = 0;
if (R.find(w[i]) != R.end())
count_w_c = R[w[i]];
P += log((double) (count_w_c + 1)/ (count_c + R.size()));
}
if (P > maxPwc)
maxPwc = P, chooseClass = it->first;
}
recordJudge(category, chooseClass);
return chooseClass;
}
void addComment(string category, string content) {
commentCount[category]++, N++;
map<string, int> &S = categories[category];
vector<string> w = normalSentence(content);
for (int i = 0; i < w.size(); i++)
S[w[i]]++;
count_category[category] += w.size(), V += w.size();
}
string igntrim(string s) {
while (s.size() > 0) {
if (isspace(s[0]) || s[0] == '.' || s[0] == ','
|| s[0] == '!' || s[0] == '"' || s[0] == '\'')
s = s.substr(1);
else {
int t = s.length();
if (isspace(s[t-1]) || s[t-1] == '.' || s[t-1] == ','
|| s[t-1] == '!' || s[t-1] == '"')
s = s.substr(0, t-1);
else
return s;
}
}
return s;
}
string str2lower(string s) {
for (int i = 0; i < s.length(); i++)
s[i] = tolower(s[i]);
return s;
}
void custom() {
set<string> common;
int first_set = 1;
for (map<string, map<string, int> >::iterator it = categories.begin();
it != categories.end(); it++) {
map<string, int> &R = it->second;
set<string> s;
for (map<string, int>::iterator jt = R.begin();
jt != R.end(); jt++)
s.insert(jt->first);
if (first_set) common = s, first_set = 0;
else {
set<string> C;
set_intersection(common.begin(), common.end(), s.begin(), s.end(), inserter(C, C.begin()));
common = C;
}
}
for (map<string, map<string, int> >::iterator it = categories.begin();
it != categories.end(); it++) {
map<string, int> &R = it->second;
for (set<string>::iterator jt = common.begin();
jt != common.end(); jt++)
R.erase(*jt);
int cnt = 0;
for (map<string, int>::iterator jt = R.begin();
jt != R.end(); jt++)
cnt += jt->second;
// printf("%d -> %d\n", count_category[it->first], cnt);
count_category[it->first] = cnt;
}
// printf("%d\n", common.size());
}
void print() {
printf("%-20s+%10s\n", string(20, '-').c_str(), string(10, '-').c_str());
for (map<string, int>::iterator it = commentCount.begin();
it != commentCount.end(); it++) {
printf("%-20s|%10d\n", it->first.c_str(), it->second);
}
printf("%-20s+%10s\n", string(20, '-').c_str(), string(10, '-').c_str());
}
void report() {
// print <judge table>
printf("|%10s|", "AC\\Assign");
for (map<string, int>::iterator it = commentCount.begin();
it != commentCount.end(); it++)
printf("%10s|", it->first.c_str());
puts("");
for (map<string, int>::iterator it = commentCount.begin();
it != commentCount.end(); it++) {
printf("|%10s|", it->first.c_str());
for (map<string, int>::iterator jt = commentCount.begin();
jt != commentCount.end(); jt++) {
printf("%10d|", judgeTable[it->first][jt->first]);
}
puts("");
}
// homework format
// recall: fraction of docs in class i classified correctly
// precision: fraction of docs assigned class i that are actually about class i
const double beta = 1;
double micro_r = 0, micro_p = 0, macro_r = 0, macro_p = 0, f1;
for (map<string, int>::iterator it = commentCount.begin();
it != commentCount.end(); it++) {
double recall = 0, precision = 0, f1 = 0;
int sumr = 0, sump = 0;
for (map<string, int>::iterator jt = commentCount.begin();
jt != commentCount.end(); jt++) {
sumr += judgeTable[it->first][jt->first];
sump += judgeTable[jt->first][it->first];
}
recall = (double) judgeTable[it->first][it->first] / sumr;
precision = (double) judgeTable[it->first][it->first] / sump;
f1 = (beta * beta + 1) * precision * recall / (beta * beta * precision + recall);
macro_r += recall, macro_p += precision;
printf("%s\n", it->first.c_str());
printf("%9s :%.10lf\n", "p", precision);
printf("%9s :%.10lf\n", "r", recall);
printf("%9s :%.10lf\n", "f", f1);
}
// for (int i = 0; i < 2; i++, puts(""))
// for (int j = 0; j < 2; j++)
// printf("%5d ", microtable[i][j]);
// [truth 0:1][classifier 0:1] = { {TN, FP}, {FN, TP} }
micro_r = (double) microtable[1][1] / (microtable[1][1] + microtable[1][0]); // TP / (TP + FN)
micro_p = (double) microtable[1][1] / (microtable[1][1] + microtable[1][0]); // TP / (TP + FP)
f1 = (beta * beta + 1) * micro_p * micro_r / (beta * beta * micro_p + micro_r);
printf("%s\n", "Micro-average");
printf("%9s :%.10lf\n", "p", micro_p);
printf("%9s :%.10lf\n", "r", micro_r);
printf("%9s :%.10lf\n", "f", f1);
macro_r /= commentCount.size();
macro_p /= commentCount.size();
f1 = (beta * beta + 1) * macro_p * macro_r / (beta * beta * macro_p + macro_r);
printf("%s\n", "Macro-average");
printf("%9s :%.10lf\n", "p", macro_p);
printf("%9s :%.10lf\n", "r", macro_r);
printf("%9s :%.10lf\n", "f", f1);
}
};
int main() {
ifstream fin("gold_standard.xml");
string xml = "", line;
while (getline(fin, line))
xml += "\n" + line;
printf("process gold_standard.xml ...\n");
XMLParser xmlDoc(xml);
StatsModel statsModel;
printf("statistics model built ...\n");
for (int i = 0; i < xmlDoc.root.child.size(); i++) {
string a = xmlDoc.root.child[i].attr["category"],
b = xmlDoc.root.child[i].content;
statsModel.addComment(a, b);
}
statsModel.print();
// statsModel.custom();
ifstream tin("test_outcome.xml");
xml = "";
while (getline(tin, line))
xml += "\n" + line;
printf("process test_outcome.xml ...\n");
XMLParser testDoc(xml);
printf("testing test_outcome.xml ...\n");
for (int i = 0; i < testDoc.root.child.size(); i++) {
statsModel.recordJudge(xmlDoc.root.child[i].attr["category"], testDoc.root.child[i].attr["category"]);
}
statsModel.report();
return 0;
}
Read More +

遺書

寫給突然逝去的自己,或者忘記自己的自己。

寫這篇時正值 11 月,剛過完 21 歲的生日一個多星期,從意識到畢業的存在時,就覺得那等待畢業的日子跟死亡倒數的日子一樣,有人快樂畢業結束大學,同時也有不少人一同結束使命。

瞻望向前,眼前不是一片光明與希望,只有曾經失敗的目標一度叫我爬去,挑戰是不錯的,但是在大多數人都不將其視為挑戰時,挑戰對我而言就是個 懲罰 、是個罪。有人會說看看別處,那處又是什麼?已經把現有的資源都鋪在地上,已經沒有辦法前往那兒。那為什麼當初這麼傻呢?的確因為笨,所以才這麼走。

《境界的彼方》從出生到現在一直是緩刑

現在大四搬出來住外面,單純沒有抽到宿舍住,大部分時間跟電腦為伴, 每餐吃飼料維生 ,走路要花半小時,來回一趟就快一個小時,人啊為什麼要吃飯?的確在沒得吃的環境下,這麼想是非常奢侈的。每天從早上起來,照著日常習慣度過一個早上、下午、晚上,走到走廊上裝水一個人都沒有,明明整排有快 20 間房間,卻好像只有一個人住似的。

於是找找蘿莉控閒聊度日 ( 騷擾度日 ),妮可、天祥大神去上班、茵可研究所、學姊弄學碩專題,剩下能一起混的只有自己。這段時間突然講到關閉部落格這件事,蘿莉控很快就收到支持者的信件,信件中這麼描述「有時候我在某些問題上想法卡住時,都可以在您的部落格上找到非常有用的解答,你是一個厲害的 programmer,但是現在無法拜訪您的部落格,希望你邀請我。」當然這封信件是用英文寫的,畢竟在 google 搜尋上,我寫的內容並沒有排名很前面。

以前我也曾經 關閉部落格 過,但是沒有人來信或者說過什麼,其實關閉的理由很簡單,那時被某題卡了很久,找不到人討論、網路上也搜不到解答討論,一氣之下關了部落格。看起來是有點幼稚,那時心裡是這麼想的「寫這麼多想法和思路嘗試幫助別人,而自己想要幫助的時候卻找不到人。」現在的情況也是被題目描述的英文耍得團團轉,看著 google 翻譯猜來猜去,不知不覺就過一個早上、下午,甚至好幾天。

我很羨慕你們,明明有好的語言溝通理解,我卻只能不斷地用猜測來彌補。

《月刊少女野崎君》現在馬上放棄和刻苦鍛鍊一年後放棄

看著不斷低落的英文,一點也不想踏出任何一步。說再好的願景都沒用,最後還是不會接納我。人會前進是因為未知,但是在兩者選擇下,未知也能變成已知。

非常謝謝之後茵可、蘿莉控、學姊、妮可、潘神、 … 等小夥伴,我明白自己的問題很大,明白自己沒有機會學得比別人好時就會放棄,不懂時就急著要答案,遇到自己不會的領域直接舉白旗,永遠推不了的遊戲坑,非常難以調教的奴 … 等。

用 uhunt 的差集找 flere - morris821028 的題目來補,明明題數是兩倍之多,但是仍然有 60 題未解之題,開了數題來寫,也爬到 2570 AC。中間只有一句話可說「這麼這麼噁心的題目!」說來也是,很多題目是 World Final 或者是區賽的鬼題,聽蘿莉控講是集訓時被強迫寫的。心想也是,一直以來沒有被強迫寫過題目,「 單純只是不想輸,我還不想輸。

想到集訓方面,開學時候去交大旁聽課程,後來放棄去聽,其中最重要的原因是「 原來四年學習差這麼多了 」旁邊都是交大學生,有資格坐在同一間教室嗎我?

《四月是你的謊言》你應該明白吧,我能一直在你身旁給你提供的幫助並不是無限的

好啦,我能寫得就盡量寫,不會寫成文章的代碼就會丟 github,如果找不到去 github 挖一挖大概會有,從來沒有私藏過,如果找不到就是遇到失落的硬碟毀損那次-空白的五個月。如果有人來需要我的話,我會盡量放出來、寫下來, 直到我還沒有怨恨這個世界為止 。隨時將代碼放出來,防止自己哪天突然沒辦法做到這件事,同時加速這資訊流動,不再於抄不抄襲、花不花時間思考,的確給予解答是不好,因為沒有思想多樣性,想要幫助跟我一樣的人,另一個我說不定正在苦惱,說不定下一個我會看到這個我。

《甘城輝煌樂園救世主》沒有工資!

現在研究所推甄結果出來,成大交大資工所已經逕行錄取。「我啊,到底有什麼資格?沒有英文的我什麼都不是,沒有我的生存之地。」再見了,未來。

Read More +

UVa 12853 - The Pony Cart Problem

Problem

現在有一台馬車繞行一個圓,馬車內側輪胎畫出小圓、外側畫出大圓,現在知道馬車內側與外側輪胎之間的距離 D,同時知道內側輪胎每滾一圈,外側輪胎會滾 N 圈,請問外側輪胎畫出來的圓周長為何?

Sample Input

1
2
3
4
3
5.00 2.00
4.20 1.44
8.03 2.01

Sample Output

1
2
3
Case 1: 62.832
Case 2: 86.365
Case 3: 100.40

Solution

感謝峻維兄熱心翻譯

假設內側輪胎畫出小圓半徑為 A,兩個輪胎的半徑 B

$$\frac{ \frac{ 2 \pi A}{2 \pi B} }{ \frac{2 \pi (A + D)}{2 \pi A} } = N \\ \Rightarrow A = \frac{D}{N-1}$$

所求為$2 \pi (A+D)$

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <stdio.h>
#include <math.h>
int main() {
int testcase, cases = 0;
const double pi = acos(-1);
double D, N, A;
scanf("%d", &testcase);
while (testcase--) {
scanf("%lf %lf", &D, &N);
A = D / (N - 1);
double ret = 2 * pi * (A + D);
printf("Case %d: %.3lf\n", ++cases, ret);
}
return 0;
}
Read More +

UVa 12849 - Mother's Jam Puzzle

Problem

有大中小三種類型的果醬瓶在三層的架子上,每一層有不同個數的果醬瓶。但是知道每一層的總果醬體積,反求大中小果醬瓶的容積。

Sample Input

1
2
3
4
5
6
7
2
3 3 1 20.00
6 0 2 20.00
6 4 0 20.00
3 0 1 6.00
0 2 2 10.00
1 3 1 10.00

Sample Output

1
2
Case 1: 1.11 3.33 6.67
Case 2: 1.00 2.00 3.00

Solution

三條方程式,三個變數。可以直接套克拉馬公式,殺雞用牛刀使用高斯消去求解。

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
#include <stdio.h>
#include <math.h>
#include <algorithm>
using namespace std;
void gaussianElimination(double mtx[][16], int n, double sol[], int nosol[]) {
#define eps 1e-6
int i, j;
for(i = 0; i < n; i++) {
int k = i;
for(j = i; j < n; j++)
if(fabs(mtx[k][i]) < fabs(mtx[j][i]))
k = j;
if(fabs(mtx[k][i]) < eps)
continue;
if(k != i) {
for(j = 0; j <= n; j++)
swap(mtx[k][j], mtx[i][j]);
}
for(j = 0; j < n; j++) {
if(i == j) continue;
for(k = n; k >= i; k--) {
mtx[j][k] -= mtx[j][i]/mtx[i][i]*mtx[i][k];
}
}
}
for(int i = 0; i < n; i++)
nosol[i] = 0;
for(i = n-1; i >= 0; i--) {
if(fabs(mtx[i][i]) < eps)
nosol[i] = 1;
else {
if(fabs(mtx[i][n]) < eps) sol[i] = 0;
else sol[i] = mtx[i][n]/mtx[i][i];
}
for(j = i+1; j < n; j++)
if(fabs(mtx[i][j]) > eps && nosol[j])
nosol[i] = 1;
}
}
int main() {
int testcase, cases = 0;
double f[16][16], ret[16];
int sol[16];
scanf("%d", &testcase);
while (testcase--) {
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 4; j++)
scanf("%lf", &f[i][j]);
}
gaussianElimination(f, 3, ret, sol);
printf("Case %d: %.2lf %.2lf %.2lf\n", ++cases, ret[0], ret[1], ret[2]);
}
return 0;
}
/*
2
3 3 1 20.00
6 0 2 20.00
6 4 0 20.00
3 0 1 6.00
0 2 2 10.00
1 3 1 10.00
*/
Read More +

UVa 12844 - Outwitting the Weighing Machine

Problem

有五個人要測體重,倆倆上去秤得到體重總和為何,現在反推每個人的體重為多少。

Sample Input

1
2
3
4
3
114 116 118 120 121 122 123 124 125 129
110 111 114 115 118 118 119 122 123 126
180 190 190 196 196 206 216 216 226 232

Sample Output

1
2
3
Case 1: 56 58 60 64 65
Case 2: 53 57 58 61 65
Case 3: 90 90 100 106 126

Solution

這題跟 10202 - Pairsumonious Numbers 一樣。

如果將體重、總和排序後 A[]SUM[],保證最小的 A[0] + A[1] = SUM[0],和 A[0] + A[2] = SUM[1] 但是不曉得 A[0] + A[3]A[1] + A[2] 哪個小,因此窮舉 A[1] + A[2] 的結果。

每一次窮舉,可以解出前三小的值,之後就能依序推出所有結果。

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
#include <stdio.h>
#include <algorithm>
#include <set>
using namespace std;
// same 10202 - Pairsumonious Numbers
int main() {
int n, m, A[50], sum[50];
int testcase, cases = 0;
scanf("%d", &testcase);
while(testcase--) {
n = 5;
m = n*(n-1)/2;
for(int i = 0; i < m; i++)
scanf("%d", &sum[i]);
sort(sum, sum+m);
printf("Case %d:", ++cases);
multiset<int> oRB;
for(int i = 2; i < m; i++)
oRB.insert(sum[i]);
int idx, flag = 0;
for(int i = 2; i < m; i++) { // A[1]+A[2] = sum[i]
if((sum[0]+sum[1]+sum[i])%2)
continue;
int tmp = (sum[0]+sum[1]+sum[i])/2;
A[0] = tmp - sum[i];
A[1] = tmp - sum[1];
A[2] = tmp - sum[0];
multiset<int> RB; // copy
multiset<int>::iterator it;
RB = oRB;
it = RB.find(sum[i]);
RB.erase(it);
int pass = 1;
for(int j = 3; j < n; j++) {
it = RB.begin();// get min
A[j] = (*it) - A[0];
RB.erase(it);
int ok = 1;
for(int k = 1; k < j; k++) { // delete A[j]+A[0-(j-1)]
int tmp = A[j] + A[k];
it = RB.find(tmp);
if(it == RB.end()) {
ok = 0;
break;
}
RB.erase(it);
}
if(!ok) {
pass = 0;
break;
}
}
if(pass) {// output ans
flag = 1;
for(int j = 0; j < n; j++)
printf(" %d", A[j]);
puts("");
break;
}
}
if(!flag)
puts("Impossible");
}
return 0;
}
/*
3
114 116 118 120 121 122 123 124 125 129
110 111 114 115 118 118 119 122 123 126
180 190 190 196 196 206 216 216 226 232
*/
Read More +

UVa 12842 - The Courier Problem

Problem

軍隊筆直等速度移動,隊伍長度 L,信使位於隊伍最後面,要傳遞訊息到隊伍前方後跑回隊伍末端。跑回末端時候,隊伍已經前進 L 距離。求信使跑的距離為何?

Sample Input

1
2
1
50

Sample Output

1
Case 1: 120.71

Solution

假設軍隊移動速度$v$, 信使移動速度$u$

看時間

$$\frac{L}{v} = \frac{L}{u-v} + \frac{L}{u+v} \\ u^{2} - v^{2} = 2v, \\ \text{Let u = 1, get } v = \frac{-2 + \sqrt{8} }{2}$$

輸出$\frac{L}{v} u$ 即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <stdio.h>
#include <math.h>
int main() {
int testcase, cases = 0;
double L;
double u = 1, v = (-2 + sqrt(8))/ 2.0;
scanf("%d", &testcase);
while (testcase--) {
scanf("%lf", &L);
double time = L / v;
printf("Case %d: %.2lf\n", ++cases, time * u);
}
return 0;
}
/*
army v, courier u
time:
L / v = L / (u - v) + L / (u + v) => u^2 - v^2 = 2v
result L / v * u, let u = 1 => v = (-2 + sqrt(8))/ 2.0.
*/
Read More +

UVa 12841 - In Puzzleland (III)

Problem

給定一個無向圖,每一個頂點由大寫字母表示,找到從起點到終點,並且經過所有點 (每一個點最多經過一次) 的路徑,輸出最小字典順序的路徑。

Sample Input

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
3
12 14
A B C D E F U V W X Y Z
A F
A V
B U
B W
C D
C V
D Y
D W
E X
E Z
F U
F Y
U Z
W X
3 2
A B C
A B
A C
4 5
L N I K
L N
I L
I N
K N
K I

Sample Output

1
2
3
Case 1: AVCDYFUBWXEZ
Case 2: impossible
Case 3: LINK

Solution

一開始想說點數最多才 15,根據 D&C 的作法,拆分成兩塊 (分別有 N/2 個點),其中一塊從起點開始、另一塊以終點結束的路徑。隨後再把兩塊組合在一起,找字典順序最小即可,看來是測資太多,很容易就 TLE,不過這方法是最穩定的,建表最慘需要 O(2^15 * 7!)

由於可行狀態太多,也就是好死不死給一張接近完全圖,那上述的算法會超慢,如果要計算方法總數可能才會用到上述解法。而這一題直接 dfs 搜索,並且確保每一步走下去時,剩下的節點會在同一個連通元件上,不會拆分成兩塊。

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
#include <stdio.h>
#include <string.h>
#include <iostream>
#include <vector>
#include <queue>
#include <algorithm>
using namespace std;
vector<int> g[26];
char path[32];
int n, m, st, ed, used[26];
int bused[26] = {}, tused = 0;
int bfs(int u, int comp) {
tused++;
int cnt = 0, v;
queue<int> Q;
Q.push(u), bused[u] = tused;
while (!Q.empty()) {
u = Q.front(), Q.pop();
cnt++;
for (int i = 0; i < g[u].size(); i++) {
v = g[u][i];
if (bused[v] != tused && used[v] == 0) {
bused[v] = tused;
Q.push(v);
}
}
}
return cnt == comp;
}
int dfs(int idx, int u) {
path[idx] = u + 'A';
if (idx < n - 1 && used[ed]) return 0;
if (idx == n-1) {
path[n] = '\0';
puts(path);
return 1;
}
for (int i = 0; i < g[u].size(); i++) {
int v = g[u][i];
if (used[v] || !bfs(v, n - idx - 1))
continue;
used[v] = 1;
if(dfs(idx+1, v)) return 1;
used[v] = 0;
}
return 0;
}
int main() {
int testcase, cases = 0;
int x, y;
scanf("%d", &testcase);
while (testcase--) {
scanf("%d %d", &n, &m);
char name[26][4], s1[4], s2[4], buf[26];
int mg[26][26] = {};
for (int i = 0; i < 26; i++)
g[i].clear();
for (int i = 0; i < n; i++)
scanf("%s", name[i]);
for (int i = 0; i < m; i++) {
scanf("%s %s", s1, s2);
x = s1[0] - 'A', y = s2[0] - 'A';
mg[x][y] = mg[y][x] = 1;
}
st = name[0][0] - 'A', ed = name[n-1][0] - 'A';
for (int i = 0; i < 26; i++) {
for (int j = 0; j < 26; j++) {
if (mg[i][j])
g[i].push_back(j);
}
}
printf("Case %d: ", ++cases);
memset(used, 0, sizeof(used));
used[st] = 1;
int f = dfs(0, st);
if (!f) puts("impossible");
}
return 0;
}
/*
3
12 14
A B C D E F U V W X Y Z
A F
A V
B U
B W
C D
C V
D Y
D W
E X
E Z
F U
F Y
U Z
W X
3 2
A B C
A B
A C
50
4 5
L N I K
L N
I L
I N
K N
K I
*/
Read More +

UVa 12109 - Fairies' Defence

Problem

在三維空間中有 N 位仙女,突然間被攻擊者定住,而攻擊者可能在這三維方體中任何一點出現,出現時會攻擊最鄰近的仙女,請問每名仙女被攻擊的機率為何?

Sample Input

1
2
3
4
5
6
7
2 3 3 3
1 1 1
2 2 2
2 7 2 10
1 1 6
3 1 6
0

Sample Output

1
2
Case 1: 0.500 0.500
Case 2: 0.286 0.714

Solution

很明顯地是一個 3D Voronoi,但是要處理 3D Voronoi 還不太行,根據 DJWS 筆記中寫道,應該使用 O(N N log N) 的做法,窮舉一點與其他點之間拉出來的半平面交,計算半平面交的面積佔有多少。

但這一題是 3D 情況,也就是說半空間交,找到凸殼之後計算其體積,因此沒有單純的半平面交這麼簡單。

如何做到半空間交目前沒有想法,但是利用蒙地卡羅算機率還算可行,每一筆測資限制窮舉次數在 800 萬內即可通過。

由於 N 很小,也想過窮舉一點之後,拉出半空間的所有情況,任三面交一點,若該點在半空間中都符合,則保留於後面處理。得到所有點集,拿來做三維凸包,之後計算體積。關於三維凸包 (凸殼) 計算體積,內部戳一點,對於所有面會形成錐體,把所有錐體體積加總即可。

也許 DJWS 的作法是正確的,但是目前不知道如何做到半空間交。不知道要怎麼繞行算法。

關於 2D 的題目,請參考 zerojudge b358. 竹馬不敵天降

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
#include <stdio.h>
#include <stdlib.h>
#include <algorithm>
using namespace std;
double mrandom() {
int r = rand();
return (double) r / RAND_MAX;
}
int main() {
int n, a, b, c;
int cases = 0;
int x[32], y[32], z[32];
while (scanf("%d", &n) == 1 && n) {
scanf("%d %d %d", &a, &b, &c);
const int runtime = 8000000;
for (int i = 0; i < n; i++)
scanf("%d %d %d", &x[i], &y[i], &z[i]);
int count[32] = {}, guess = runtime / n;
double tx, ty, tz;
for (int i = guess - 1; i >= 0; i--) {
tx = mrandom() * a, ty = mrandom() * b, tz = mrandom() * c;
double dist = 1e+60, tmp;
int mn = 0;
for (int j = 0; j < n; j++) {
tmp = (tx - x[j]) * (tx - x[j]) + (ty - y[j]) * (ty - y[j]) +
(tz - z[j]) * (tz - z[j]);
if (tmp < dist)
dist = tmp, mn = j;
}
count[mn]++;
}
printf("Case %d:", ++cases);
for (int i = 0; i < n; i++)
printf(" %.3lf", (double) count[i] / guess);
puts("");
}
return 0;
}
Read More +

UVa 11931 - Maze Escape

Problem

類似倉庫番遊戲,必須將箱子推到指定的按鈕位置,才會開啟逃離門,每一次可以走一步,如果箱子在旁邊,則人與箱子會同時往同一個方向移動。求逃離最少步數。

在還沒有打開門之前,門就相當於障礙物存在,不能經過。

Sample Input

1
2
3
4
5
6
7
8
9
10
11
12
3 3
d.b
.@.
x.#
3 5
...d.
.#x#.
...@b
0 0

Sample Output

1
2
No Way!
20

Solution

反例輸出有誤,把 No Way! 拆成兩行送出結果拿到 WA。

把人的位置和箱子位置記錄成一個狀態,得到 dist[sx][sy][bx][by] 進行 bfs 搜索即可。特別小心不可經過尚未開啟的門問題。

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
#include <stdio.h>
#include <string.h>
#include <queue>
using namespace std;
int sx, sy, ex, ey, bx, by, cx, cy;
int n, m;
char g[32][32];
const int dx[] = {0, 0, 1, -1};
const int dy[] = {1, -1, 0, 0};
int dist[25][25][25][25];
int bfs() {
memset(dist, 0, sizeof(dist));
dist[sx][sy][bx][by] = 1;
queue<int> X, Y, BX, BY;
int x, y, tx, ty, ttx, tty;
X.push(sx), Y.push(sy), BX.push(bx), BY.push(by);
while (!X.empty()) {
x = X.front(), X.pop();
y = Y.front(), Y.pop();
bx = BX.front(), BX.pop();
by = BY.front(), BY.pop();
int step = dist[x][y][bx][by];
for (int i = 0; i < 4; i++) {
tx = x + dx[i], ty = y + dy[i];
if (tx < 0 || ty < 0 || tx >= n || ty >= m)
continue;
if (g[tx][ty] == 'd' && g[bx][by] == 'b')
return step;
if (g[tx][ty] == '#' || g[tx][ty] == 'd')
continue;
if (tx == bx && ty == by) { // push it
ttx = tx + dx[i], tty = ty + dy[i];
if (ttx < 0 || tty < 0 || ttx >= n || tty >= m)
continue;
if (g[ttx][tty] == '#' || g[ttx][tty] == 'd')
continue;
if (dist[tx][ty][ttx][tty] == 0) {
dist[tx][ty][ttx][tty] = step + 1;
X.push(tx), Y.push(ty), BX.push(ttx), BY.push(tty);
}
} else {
if (dist[tx][ty][bx][by] == 0) {
dist[tx][ty][bx][by] = step + 1;
X.push(tx), Y.push(ty), BX.push(bx), BY.push(by);
}
}
}
}
return -1;
}
int main() {
while (scanf("%d %d", &n, &m) == 2 && n) {
for (int i = 0; i < n; i++)
scanf("%s", g[i]);
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
if (g[i][j] == '@')
sx = i, sy = j;
if (g[i][j] == 'd')
ex = i, ey = j;
if (g[i][j] == 'x')
bx = i, by = j;
if (g[i][j] == 'b')
cx = i, cy = j;
}
}
int ret = bfs();
if (ret < 0)
puts("No Way!");
else
printf("%d\n", ret);
}
return 0;
}
/*
3 3
d.b
.@.
x.#
3 5
...d.
.#x#.
...@b
0 0
*/
Read More +