批改娘 10081. Fast Game of Life

contents

  1. 1. 題目描述
  2. 2. 輸入格式
  3. 3. 輸出格式
  4. 4. 範例輸入 1
  5. 5. 範例輸出 1
  6. 6. 範例輸入 2
  7. 7. 範例輸出 2
  8. 8. Solution
    1. 8.1. 循環展開

題目描述

生命遊戲中,對於任意細胞,規則如下:
每個細胞有兩種狀態-存活或死亡,每個細胞與以自身為中心的周圍八格細胞產生互動。

  • 當前細胞為存活狀態時,當周圍低於 2 個 (不包含 2 個) 存活細胞時,該細胞變成死亡狀態。
  • 當前細胞為存活狀態時,當周圍有 2 個或 3 個存活細胞時, 該細胞保持原樣。
  • 當前細胞為存活狀態時,當周圍有 3 個以上的存活細胞時,該細胞變成死亡狀態。
  • 當前細胞為死亡狀態時,當周圍有 3 個存活細胞時,該細胞變成存活狀態。

可以把最初的細胞結構定義為種子,當所有在種子中的細胞同時被以上規則處理後,可以得到第一代細胞圖。按規則繼續處理當前的細胞圖,可以得到下一代的細胞圖,周而復始。

輸入格式

輸入第一行有兩個整數 $N$, $M$,表示盤面大小為 $N \times N$,模擬週期次數 $M$。接下來會有 $N$ 行,每一行上會有 $N$ 個字符,以 0 表示 $(i, j)$ 格子上的細胞屬於死亡狀態,反之 1 為存活狀態。

  • $1 \le N \le 2000$
  • $0 \le M \le 1000$

輸出格式

對於每一組測資輸出 $N$ 行,每一行上有 $N$ 個字元表示模擬 $M$ 次的最終盤面結果。

範例輸入 1

1
2
3
4
5
6
5 1
10001
00100
01110
00100
01010

範例輸出 1

1
2
3
4
5
00000
00100
01010
00000
00100

範例輸入 2

1
2
3
4
5
6
5 3
10001
00100
01110
00100
01010

範例輸出 2

1
2
3
4
5
00000
00000
01110
00000
00000

Solution

在還沒有做任何平行前,生命遊戲的模擬非常簡單,只需要統計鄰近八格的狀態即可,然而由於是常數範圍,直接打八個加法比起迴圈去累計八格的結果快上非常多,別忽視 for loop 在做 branch 的 cycle 數量。

  • 樸素平行 Accepted (378 ms, 24 MB)
  • 循環展開 Accepted (289 ms, 39 MB)

平行採用 big chunk 的方式,考慮到 cache miss 的關係,最好是每一個處理器都把相鄰的列放置在各自的 L1-L2-L3 cache,防止使用時不在自己的 cache,而產生嚴重的 cache miss。

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
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <assert.h>
#include <omp.h>
#define UINT unsigned long
#define MAXN 2048
char g[2][MAXN][MAXN];
int n, m;
int main() {
while (scanf("%d %d", &n, &m) == 2) {
while (getchar() != '\n');
// read input
for (int i = 1; i <= n; i++) {
fgets(g[0][i]+1, MAXN, stdin);
for (int j = 1; j <= n; j++)
g[0][i][j] -= '0';
g[0][i][n+1] = 0;
}
// game of life
int flag = 0;
int chunk = 64;
for (int it = 0; it < m; it++) {
#pragma omp parallel for schedule(static, chunk)
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
int adj = g[flag][i-1][j-1] + g[flag][i-1][j] + g[flag][i-1][j+1] +
g[flag][i ][j-1] + g[flag][i ][j+1] +
g[flag][i+1][j-1] + g[flag][i+1][j] + g[flag][i+1][j+1];
g[!flag][i][j] = (g[flag][i][j] == 0 && adj == 3) ||
(g[flag][i][j] == 1 && (adj == 2 || adj == 3));
}
}
flag = !flag;
}
// print result
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++)
g[flag][i][j] += '0';
g[flag][i][n+1] = '\0';
puts(g[flag][i]+1);
}
}
return 0;
}

循環展開

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
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <assert.h>
#include <omp.h>
#define UINT unsigned long
#define MAXN 2048
char g[2][MAXN][MAXN];
int n, m;
int main() {
while (scanf("%d %d", &n, &m) == 2) {
while (getchar() != '\n');
// read input
for (int i = 1; i <= n; i++) {
fgets(g[0][i]+1, MAXN, stdin);
for (int j = 1; j <= n; j++)
g[0][i][j] -= '0';
g[0][i][n+1] = 0;
}
// game of life
#ifdef _OPENMP
int chunk = n / omp_get_max_threads();
#endif
for (int it = 0; it < m; it++) {
#pragma omp parallel for schedule(static, chunk)
for (int i = 1; i <= n; i++) {
int adj, j = 1;
#define UNLOOP { \
adj = g[0][i-1][j-1] + g[0][i-1][j] + g[0][i-1][j+1] + \
g[0][i ][j-1] + g[0][i ][j+1] + \
g[0][i+1][j-1] + g[0][i+1][j] + g[0][i+1][j+1]; \
g[1][i][j] = (g[0][i][j] == 0 && adj == 3) || \
(g[0][i][j] == 1 && (adj == 2 || adj == 3)); j++; \
}
#define UNLOOP4 {UNLOOP UNLOOP UNLOOP UNLOOP}
#define UNLOOP8 {UNLOOP4 UNLOOP4}
for (; j + 8 <= n; )
UNLOOP8;
for (; j <= n; )
UNLOOP;
}
#pragma omp parallel for schedule(static, chunk)
for (int i = 1; i <= n; i++)
memcpy(g[0][i], g[1][i], sizeof(g[0][0][0]) * (n+1));
}
// print result
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++)
g[0][i][j] += '0';
g[0][i][n+1] = '\0';
puts(g[0][i]+1);
}
}
return 0;
}