批改娘 10086. Red/Blue Computation

題目描述

模擬工作流程,在一個 $N \times N$ 的網格 (左邊界可以通到右邊界,上邊界可以通到下邊界) 上有三種狀態紅 $R$, 藍 $B$, 空格 $W$,每次模擬分成兩個步驟

  • 第一步,只有紅 $R$ 可移動,紅 $R$ 只能往右移動一格到空白 $W$ 的位置,否則仍在原處。
  • 第二步,只有藍 $B$ 可移動,藍 $B$ 只能往下移動一格到空白 $W$ 的位置,否則仍在原處。

請問模擬 $M$ 次後盤面為何?

輸入格式

輸入只有一組測資,第一行有兩個整數 $N, \; M$,分別為盤面大小以及模擬次數。接下來會有 $N$ 行,每一行上會有 $N$ 個字元。

  • $1 \le N, M \le 1000$

輸出格式

輸出 $N \times N$ 盤面。

範例輸入

1
2
3
4
5
4 1
RRWR
WWBW
BWRW
WWWW

範例輸出

1
2
3
4
RWRR
WWWW
WWBR
BWWW

備註






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
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define MAXN 1024
#define C_WHITE 0
#define C_RED 1
#define C_BLUE 2
char g1[MAXN][MAXN], g2[MAXN][MAXN];
void moveRed(int n) {
#pragma omp parallel for schedule(static)
for (int i = 0; i < n; i++)
memset(g2[i], C_WHITE, sizeof(char) * n);
#pragma omp parallel for schedule(static)
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
if (g1[i][j] == C_BLUE) {
g2[i][j] = C_BLUE;
} else if (g1[i][j] == C_RED) {
int next = j+1 == n ? 0 : j+1;
if (g1[i][next] == C_WHITE)
g2[i][next] = C_RED;
else
g2[i][j] = C_RED;
}
}
}
}
void moveBlue(int n) {
#pragma omp parallel for schedule(static)
for (int i = 0; i < n; i++)
memset(g1[i], C_WHITE, sizeof(char) * n);
#pragma omp parallel for schedule(static)
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
if (g2[i][j] == C_RED) {
g1[i][j] = C_RED;
} else if (g2[i][j] == C_BLUE) {
int next = i+1 == n ? 0 : i+1;
if (g2[next][j] == C_WHITE)
g1[next][j] = C_BLUE;
else
g1[i][j] = C_BLUE;
}
}
}
}
static inline int toIndex(char c) {
if (c == 'W') return C_WHITE;
if (c == 'R') return C_RED;
if (c == 'B') return C_BLUE;
fprintf(stderr, "[WARNING] %s %d\n", __FILE__, __LINE__);
}
static void printBoard(int n) {
char color[4] = "WRB";
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++)
putchar(color[g1[i][j]]);
putchar('\n');
}
}
int main() {
int n, m;
while (scanf("%d %d", &n, &m) == 2) {
for (int i = 0; i < n; i++) {
scanf("%s", &g1[i]);
for (int j = 0; j < n; j++)
g1[i][j] = toIndex(g1[i][j]);
}
for (int it = 0; it < m; it++) {
moveRed(n);
moveBlue(n);
}
printBoard(n);
}
return 0;
}
/*
4 1
RRWR
WWBW
BWRW
WWWW
*/
Read More +

2016 Google Code Jam Round 1A

A. The Last Word

每一字串 $S$,可視為一串操作,依序讀入一個字元 $C$,可以選擇把字元 $C$ 插入字串首或尾,請問字典順序最大為何?

類似 ZJ. 一串數字 之類的貪心方式,優先考慮最大字元在哪一個位置,抓取之後拆分兩串分開貪心合併,每一次由後往前找到第一個最大字典順序字元 $T$$T$ 移到最前方,此時會將字串分成前半 $A$ 和後半 $B$,然而後半 $B$ 要排在 $T$ 字元之後,勢必只能按照順序丟入隊尾。因此 $\textit{MAXEXPR}(F) = T + \textit{MAXEXPR}(A) + B$

B. Rank and File

給定一個 $N \times N$ 方格,每一行或列都是嚴格遞增,現在給定數個行、數個列的排列情況,請問缺漏的那一行或列的序列為何?

明顯地每一個數字都恰好出現偶數次,按照大小順序印出奇數次數字即可。

C. BFFs

給同學都有一位摯友,給定 $N$ 個人的好友資訊,現在要求盡可能多的人圍成一圈,並且每一個人其中一側是他們自己的摯友,請問圈最多幾個人。

明顯地,若不看方向性,每一個連通圖至多一個環。若一個連通圖式一個環,保證無法與其他方案合併在一起,因此用 $\mathcal{O}(N^2)$ 優先排除是環的解,在其中找最大圈即可。

接著考慮有觸手的水母圖,當水母大小恰好由兩個點構成時才會有解,因為三個點以上不符合題目要求的環。最後找到經過兩端點的最長觸手,這些點可以自我滿足,把所有最長觸手合併在一起可以構成一組特殊解。兩種方案取最大。

Solution

A

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
#include <bits/stdc++.h>
using namespace std;
string dfs(int n, string s) {
if (n == 0) return "";
char mx = s[0];
for (int i = 0; i < n; i++)
mx = max(mx, s[i]);
for (int i = n-1; i >= 0; i--) {
if (s[i] == mx) {
string mm = string(1, mx);
return mm + dfs(i, s.substr(0, i)) + s.substr(i+1) ;
}
}
return "";
}
int main() {
int testcase, cases = 0;
scanf("%d", &testcase);
while (testcase--) {
char cs[1024];
scanf("%s", cs);
int n = strlen(cs);
string v = dfs(n, cs);
printf("Case #%d: %s\n", ++cases, v.c_str());
}
return 0;
}

B

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
#include <bits/stdc++.h>
using namespace std;
int main() {
int testcase, cases = 0;
scanf("%d", &testcase);
while (testcase--) {
int n, m, x;
map<int, int> R;
scanf("%d", &n);
m = 2*n-1;
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
scanf("%d", &x);
R[x]++;
}
}
printf("Case #%d:", ++cases);
for (auto e : R) {
if (e.second % 2 == 1)
printf(" %d", e.first);
}
puts("");
}
return 0;
}

C

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
#include <bits/stdc++.h>
using namespace std;
int g[1024][1024];
vector<int> gg[1024];
int cc[1024];
int dfs(int u, int dep) {
int ret = dep;
for (auto v : gg[u]) {
if (cc[v]) continue;
int tmp = dfs(v, dep+1);
ret = max(ret, tmp);
}
return ret;
}
int main() {
int testcase, cases = 0;
scanf("%d", &testcase);
while (testcase--) {
int n, A[1024];
memset(g, 0, sizeof(g));
scanf("%d", &n);
for (int i = 1; i <= n; i++)
gg[i].clear();
for (int i = 1; i <= n; i++)
scanf("%d", &A[i]), g[i][A[i]] = 1, gg[A[i]].push_back(i);
int ret = 0;
for (int i = 1; i <= n; i++) {
int used[1024] = {};
int u = i, cnt = 0;
for (; used[u] == 0; u = A[u]) {
used[u] = 1, cnt++;
}
if (u == i)
ret = max(ret, cnt);
}
memset(cc, 0, sizeof(cc));
int cctot = 0;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
if (i == j) continue;
if (cc[i] || cc[j]) continue;
if (g[i][j] == 0 || g[j][i] == 0)
continue;
cc[i] = 1, cc[j] = 1;
cctot += 2;
cctot += dfs(i, 0) + dfs(j, 0);
}
}
ret = max(ret, cctot);
printf("Case #%d: %d\n", ++cases, ret);
}
return 0;
}
Read More +

2016 Google Code Jam Round QR

一年一度的 GCJ 資格賽又開打了,這次的題目類型挺開放的,也就是有很多不同的解法可以通過。也許是因為都用了奇怪的方法解決。全寫完 Rank 400 名以內。

打完比賽完全沒心情寫水泥數學的作業證明。不!其實是不會寫。

A. Counting Sheep

睡覺前數羊,從一個基數 $N$ 開始,數 $N$, $2N$, $3N$, …, $?N$,直到 10 個位數都出現過,請問最後一個數的數 $?N$ 為何。

除了 $N = 0$ 的特殊情況外,直接模擬即可。最重要的是它不只有看個位數字,把每一位全看的狀況下,要湊滿 0-9 所有 digits 都出現過就不是難事。

B. Revenge of the Pancakes

現在 $N$ 個煎餅位於堆上,並且有分成兩個正反面狀態,每一次可以把煎餅從堆頂開始連續得拿出,並反轉序列放回堆中,同時把煎餅正反反轉,請問從一個開始序列轉移到全部皆為正面的操作次數最少為何?

一開始的策略是亂貪心一番,甚至題目看錯都能亂過小測資。必要任務一定是先把堆底那片煎餅翻成正的,這時一定堆頂為反,進行一次反轉讓堆底便成正的,為了讓堆頂為反,貪心策略盡可能讓堆頂一連續全部變成反。不斷地模擬此步驟即可。

C. Coin Jam

產生一個數字 $X$$X$ 長度恰為 $N$,必須首尾皆為 1,只能由 0/1 構成。請產生數字 $X$,滿足在二進制、三進制 … 到十進制下皆為合成數。

這題莫名其妙指定 $N = 16, \; 32$,特殊的 $N$ 使得找到 $J$$X$ 並不難,更由於 $J$ 不大,各種解法都可以完成。從以往得概念,假設有一個數字 $A = 123123$,那麼保證不進位的情況下,找得到一組 $A = 123 \times 1001$,因此指定 $A = 1????1$,湊滿 $X = AA \cdots A$,不管在哪個進制下,勢必被 $A$ 整除。

D. Fractiles

詭異的數學碎形考古,給一個長度為 $K$ 的起始字串 $X$$X$ 只由 $G$$L$ 構成。碎形迭代 $C$ 次,產生長度為 $K^C$ 的字串。現在雇用 $S$ 個學生,每個學生只能檢查一個位子,只要確定那一個位子有 $G$,那麼原始字串保證有 $G$。在限定學生數量下,請問要怎麼安排學生們的檢查工作。

由於長度為 $K$ 的起始字串有 $2^K$ 種,實際上只要剃除 $LL \cdots L$ 的字串。為了要在最少檢查位置下處理,必然要一個位置能否有 $G$,就能檢查原始字串的數個位置是否有 $G$

窮舉只有 1 個 $G$ 個字串 $GLL...L$$LGL...L$、… 等 $K$ 個,追蹤迭代 $C$ 次後,$K^C$ 長度下,每一個字串 $G$ 的出現位置。當 $C = 2$,可以讓位置 1, 2 的 $G$ 可以同時被檢查,同理位置 3, 4 的 $G$ 可以同時被檢查。當 $C = 3$ 時,位置 1, 2, 3 的 $G$ 可以同時被檢查。根據觀察,至少要 $\textit{ceil}(K/C)$ 個學生就能滿足需求。

$i$ 個學生要檢查的位置為 $1 + \sum_{0 \le j < C} (C(i-1)+(C-1-j))K^j$

Solution

A

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
#include <bits/stdc++.h>
using namespace std;
int main() {
int testcase, cases = 0;
scanf("%d", &testcase);
while (testcase--) {
long long n;
scanf("%lld", &n);
if (n == 0) {
printf("Case #%d: INSOMNIA\n", ++cases);
continue;
}
int has[128] = {}, ten = 10;
long long on = n;
while (1) {
static char buf[32];
sprintf(buf, "%lld", n);
for (int i = 0; buf[i]; i++) {
if (has[buf[i]] == 0) {
ten--, has[buf[i]] = 1;
}
}
if (ten == 0)
break;
n += on;
}
printf("Case #%d: %lld\n", ++cases, n);
}
return 0;
}

B

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
#include <bits/stdc++.h>
using namespace std;
int main() {
int testcase, cases = 0;
char s[1024];
scanf("%d", &testcase);
while (testcase--) {
scanf("%s", s);
int n = strlen(s), ret = 0;
for (int i = n-1; i >= 0; i--) {
if (s[i] == '+') continue;
int has = 0, j = 0;
for (j = 0; j <= i; j++) {
if (s[j] == '-') break;
has = 1;
}
if (has) {
reverse(s, s+j);
for (int k = 0; k < j; k++) {
if (s[k] == '-')
s[k] = '+';
else
s[k] = '-';
}
}
ret += has;
if (s[i] == '+') continue;
reverse(s, s+i+1);
for (int k = 0; k < i+1; k++) {
if (s[k] == '-')
s[k] = '+';
else
s[k] = '-';
}
ret++;
}
printf("Case #%d: %d\n", ++cases, ret);
}
return 0;
}

C

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
#include <bits/stdc++.h>
using namespace std;
int main() {
int testcase, cases;
scanf("%d", &testcase);
while (testcase--) {
int N, J;
scanf("%d %d", &N, &J);
printf("Case #%d:\n", ++cases);
set<long long> R;
for (int i = 2; i < N && J; i++) {
if (N%i) continue;
for (int j = 0; j < (1<<(i-2)) && J; j++) {
int mask = (j<<1)|1|(1<<(i-1));
long long v = 0;
for (int k = 0; k < N/i; k++)
v = (v<<i) | mask;
if (R.count(v)) continue;
R.insert(v);
for (int k = N-1; k >= 0; k--)
printf("%d", (v>>k)&1);
for (int B = 2; B <= 10; B++) {
long long div = 0, nn = 0;
for (int k = i-1; k >= 0; k--)
div = div * B + ((mask>>k)&1);
for (int k = N-1; k >= 0; k--)
nn = nn * B + ((v>>k)&1);
// assert(nn % div == 0 && nn != div);
printf(" %lld", div);
}
puts("");
J--;
}
}
assert(J == 0);
}
return 0;
}
/*
1
16 3
*/

D

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 <bits/stdc++.h>
using namespace std;
long long mpow(long long x, long long y) {
long long ret = 1;
while (y) {
if (y&1)
ret = ret * x;
y >>= 1, x = x*x;
}
return ret;
}
int main() {
int testcase, cases = 0;
scanf("%d", &testcase);
while (testcase--) {
long long K, C, S;
scanf("%lld %lld %lld", &K, &C, &S);
printf("Case #%d:", ++cases);
int pos = 0;
vector<unsigned long long> A;
// puts("");
for (int i = 0; i < ceil(1.f*K/C); i++) {
int item = min(C, K - pos);
unsigned long long x = 0;
for (int j = C-1, k = 0; j >= 0 && k < item; j--, k++) {
// printf("%d K^%d + ", pos + k, j);
x += (pos + k) * mpow(K, j);
}
A.push_back(x);
// puts("");
pos += C;
if (pos > K) pos = K;
}
if (A.size() > S)
puts(" IMPOSSIBLE");
else {
for (int i = 0; i < A.size(); i++)
printf(" %llu", A[i]+1);
puts("");
}
}
return 0;
}
/*
5
4 4 5
*/
Read More +

數據分割 區間分塊小技巧

前言

運行平行程式之前,要將資料切成很多塊,每一塊之間獨立。從邏輯上看來有 row-based, column-based, matrix-based, tree-based 等平行方法。在許多方法中,最容易入手的就是 row-based。

假設有 $n$ 個元素,編號介於 $[0, n-1]$ 之間,要分成 $m$ 組。每一組得編號都要連續,以增加資料局部性 (data locality) 並且每一塊大小盡可能一樣,請問要怎麼分割才好?這樣的數學問題要做出來並不是件難事,但強迫症的人來說,在程式中零碎的判斷相當折騰。

數學分析

直觀

若有 $n$ 個元素分成 $m$ 組,每一個組至少有 $\lfloor n / m \rfloor$ 個元素,其中 $n \mod m$ 組會額外多一個元素。因此,對於第 $i$ 組滿足 $i < n \mod m$ 都會多一個元素。

1
2
3
4
5
6
7
8
9
10
void method0(int n, int m) {
printf("In %s\n", __func__);
int bsz = n/m, rm = n%m, sz;
for (int i = 0, L = 0, R; i < m; i++) {
sz = bsz + (i < rm);
R = L + sz - 1;
printf("%d len([%d, %d]) = %d\n", i, L, R, (R - L + 1));
L = R + 1;
}
}

程序流

這方式屬於懶惰程序員,根據整數的性質會得到刻度,在計算第 $i$ 個刻度採用 $\lfloor \frac{in}{m} \rfloor$,自然而然地可以與下一個刻度形成一個區間。不幸地,若 $m > n$ 時,有些刻度會重疊,導致重複計算的情況發生。

1
2
3
4
5
6
7
void method1(int n, int m) {
printf("In %s\n", __func__);
for (int i = 0, L, R; i < m; i++) {
L = i*n/m, R = (i+1)*n/m - 1;
printf("%d len([%d, %d]) = %d\n", i, L, R, (R - L + 1));
}
}

ceil

從《具體數學》第三章節的介紹,得到幾個簡單的數學公式

$$\begin{align} n = \left \lceil \frac{n}{m} \right \rceil + \left \lceil \frac{n-1}{m} \right \rceil + \cdots + \left \lceil \frac{n-m-1}{m} \right \rceil \end{align}$$

$i$ 團 ($0 \le i < m$),要處理 $\left \lceil \frac{n-i}{m} \right \rceil$ 個元素。

1
2
3
4
5
6
7
8
9
void method2(int n, int m) {
printf("In %s\n", __func__);
for (int i = 0, L = 0, R, sz; i < m; i++) {
sz = (n + m-i-1) / m;
R = L + sz-1;
printf("%d len([%d, %d]) = %d\n", i, L, R, sz);
L = R + 1;
}
}

floor

又或者使用 floor 表示法

$$\begin{align} n = \left \lfloor \frac{n}{m} \right \rfloor + \left \lfloor \frac{n+1}{m} \right \rfloor + \cdots + \left \lfloor \frac{n+m-1}{m} \right \rfloor \end{align}$$

$i$ 團 ($0 \le i < m$),要處理 $\left \lceil \frac{n+i}{m} \right \rceil$ 個元素。

1
2
3
4
5
6
7
8
9
void method3(int n, int m) {
printf("In %s\n", __func__);
for (int i = 0, L = 0, R, sz; i < m; i++) {
sz = (n + i) / m;
R = L + sz-1;
printf("%d len([%d, %d]) = %d\n", i, L, R, sz);
L = R + 1;
}
}

補充流

不管怎樣,前 $i$ 就拿取 $\lceil \frac{n}{m} \rceil$,最後一組拿少一點。

1
2
3
4
5
6
7
8
9
void method4(int n, int m) {
printf("In %s\n", __func__);
int bsz = (n + m-1)/m, sz;
for (int i = 0, L = 0, R; i < m; i++) {
L = i*bsz, R = L + bsz - 1;
if (R >= n) R = n-1;
printf("%d len([%d, %d]) = %d\n", i, L, R, (R - L + 1));
}
}

測試

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
#include <bits/stdc++.h>
using namespace std;
void method0(int n, int m) {
printf("In %s\n", __func__);
int bsz = n/m, rm = n%m, sz;
for (int i = 0, L = 0, R; i < m; i++) {
sz = bsz + (i < rm);
R = L + sz - 1;
printf("%d len([%d, %d]) = %d\n", i, L, R, (R - L + 1));
L = R + 1;
}
}
void method1(int n, int m) {
printf("In %s\n", __func__);
for (int i = 0, L, R; i < m; i++) {
L = i*n/m, R = (i+1)*n/m - 1;
printf("%d len([%d, %d]) = %d\n", i, L, R, (R - L + 1));
}
}
void method2(int n, int m) {
printf("In %s\n", __func__);
for (int i = 0, L = 0, R, sz; i < m; i++) {
sz = (n + m-i-1) / m;
R = L + sz-1;
printf("%d len([%d, %d]) = %d\n", i, L, R, sz);
L = R + 1;
}
}
void method3(int n, int m) {
printf("In %s\n", __func__);
for (int i = 0, L = 0, R, sz; i < m; i++) {
sz = (n + i) / m;
R = L + sz-1;
printf("%d len([%d, %d]) = %d\n", i, L, R, sz);
L = R + 1;
}
}
void method4(int n, int m) {
printf("In %s\n", __func__);
int bsz = (n + m-1)/m, sz;
for (int i = 0, L = 0, R; i < m; i++) {
L = i*bsz, R = L + bsz - 1;
if (R >= n) R = n-1;
printf("%d len([%d, %d]) = %d\n", i, L, R, (R - L + 1));
}
}
int main() {
void (*FUNC[])(int, int) = {method0, method1, method2, method3, method4};
int n, m;
while (scanf("%d %d", &n, &m) == 2) {
for (int i = 0; i < sizeof(FUNC)/sizeof(FUNC[0]); i++)
(*FUNC[i])(n, m);
}
return 0;
}
Read More +

批改娘 10085. Parallel Count (debug)

題目描述

這有一份由 pthread 撰寫的序列總和計算,假設不開任何的優化參數,在快取處理會有嚴重缺失。

main.c

輸入序列長度 $n$,計算 $m$ 次經由 $\text{key}, \; \text{key} + 1, \; \text{key} + 2, \; \cdots, \; \text{key} + m-1$ 加密的序列總和。這部份將不提供修改。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <stdio.h>
#include <stdlib.h>
#include "utils.h"
int main() {
int cases = 0;
int n, m, key;
scanf("%d %d %d", &n, &m, &key);
for (int it = 0; it < m; it++) {
int ret = run(n, key);
printf("Case #%d: %d\n", ++cases, ret);
key++;
}
return 0;
}

utils.h

計算工作交由 void f(int n, int key, int *p1, int *p2, int *p3, int *p4); 完成最後四個參數,將會由 4 個 thread 計算分別儲存在位址 p1, p2, p3, p4 中。

1
2
3
4
5
6
7
#ifndef __UTILS_H
#define __UTILS_H
void f(int n, int key, int *p1, int *p2, int *p3, int *p4);
int run(int n, int key);
#endif

sum.c

平行計算序列總和,特別注意到 void* subtask(void* argu) 中存取的記憶體位置。

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
#include <stdlib.h>
#include <pthread.h>
#include <stdint.h>
#include "utils.h"
typedef struct Argu {
int *result;
int L, R, key;
} Argu;
static inline uint32_t rotate_left(uint32_t x, uint32_t n) {
return (x << n) | (x >> (32-n));
}
static inline uint32_t encrypt(uint32_t m, uint32_t key) {
return (rotate_left(m, key&31) + key)^key;
}
static void* subtask(void* argu) {
Argu *ptr = (Argu *) argu;
*(ptr->result) = 0;
for (int i = ptr->L; i <= ptr->R; i++)
*(ptr->result) += encrypt(i, ptr->key);
}
void f(int n, int key, int *p1, int *p2, int *p3, int *p4) {
pthread_t threads[4];
int *output[4] = {p1, p2, p3, p4};
for (int i = 0; i < 4; i++) {
Argu *argu = (Argu *) malloc(sizeof(Argu));
argu->result = output[i];
argu->L = i * (n / 4) + 1;
argu->R = argu->L + n / 4 - 1;
argu->key = key;
if (i == 3) argu->R = n;
pthread_create(&threads[i], NULL, subtask, argu);
}
for (int i = 0; i < 4; i++)
pthread_join(threads[i], NULL);
}

job.c

你的工作要改善下方代碼的效能。

1
2
3
4
5
6
7
8
9
10
#include "utils.h"
int ret[128];
int run(int n, int key) {
int sum = 0;
f(n, key, ret, ret+1, ret+2, ret+3);
for (int i = 0; i < 4; i++)
sum += ret[i];
return sum;
}

輸入格式

輸入只有一行三個整數 $n, \; m, \; \text{key}$

輸出格式

輸出 $m$ 序列總和結果 (無視 overflow)。

範例輸入

1
10000000 10 514

範例輸出

1
2
3
4
5
6
7
8
9
10
Case #1: 1397862656
Case #2: 1970821632
Case #3: -1178356736
Case #4: 1113221120
Case #5: 1401409536
Case #6: 1977786368
Case #7: -1164427264
Case #8: 1145914243
Case #9: 645957382
Case #10: 1308383748

編譯環境

Makefile

1
2
3
4
5
6
CFLAG=-std=c99 -pthread
all: main.c sum.c job.c
gcc $(CFLAG) main.c -c
gcc $(CFLAG) sum.c -c
gcc $(CFLAG) main.o sum.o job.c -o job

reference

Solution

由於茵可大神給予我參考的簡報 Mind Cache 中提到不少的快取實驗,其中一部份就是在 thread 使用上,於是就拿來出題測試測試一番。

通常講到數據局部性 (Data Locality) 都希望資料盡量集中,但一不小心會犯下錯誤,就是在多個 thread 儲存答案時,雖然不會共用同一個位址,但相鄰的位址在另一個核 (core) 使用,由於彼此之間相互修改,兩個相鄰位址若放在同一個 cache line,dirty bit 勢必要讓他們從 cache line 掉到 L1 -> L2 -> L3,進行資料同步。

目前 cache line 普遍上設計大小為 64 Bytes,相當於間隔 16 個 4 bytes Integer 的差距,所以儲存答案間隔遠一點會比近一點好。當然,編譯器優化參數開到 O2 以上時,似乎就直接先存放到暫存器裡頭,不會不斷地存取 global memory 部份,如此一來,就不會發生 cache miss 問題。實驗結果效能可以差到四倍左右。

1
2
3
4
5
6
7
8
9
#include "utils.h"
int ret[128];
int run(int n, int key) {
int sum = 0;
f(n, key, ret+0, ret+16, ret+32, ret+48);
sum = ret[0] + ret[16] + ret[32] + ret[48];
return sum;
}
Read More +

批改娘 10084. Prefix Sum (pthread)

題目描述

給予序列 $A \left[ 1 \cdots n \right]$,請計算前綴和序列 $S \left[ 1 \cdots n \right]$,其中 $$S \left[ i \right] = \sum_{k=1}^{i} A \left[ k \right]$$

為了檢測方便,移除輸入和輸出時間,序列 $A$ 由一個簡單加密 $\textit{key}$ 得到序列 $$A \left[ i \right] = \textit{encrypt}(i, \textit{key})$$以及輸出部分直接呼叫$\textit{output}(\textit{S}, n)$。注意$S\left[ 0 \right] = 0$,儲存答案的範圍為$S\left[ 1 \cdots n \right]$。

utils.h

可以直接 #include "utils.h" 在你的程式中。

1
2
3
4
5
6
7
8
9
10
11
#ifndef _UTILS_H
#define _UTILS_H
#include <stdint.h>
static inline uint32_t rotate_left(uint32_t x, uint32_t n) {
return (x << n) | (x >> (32-n));
}
static inline uint32_t encrypt(uint32_t m, uint32_t key) {
return (rotate_left(m, key&31) + key)^key;
}
void output(uint32_t presum[], int n);
#endif

prefixsum-seq.c

請修改這份程序。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <inttypes.h>
#include "utils.h"
#define MAXN 10000005
#define MAX_THREAD 4
uint32_t prefix_sum[MAXN];
int main() {
int n;
uint32_t key;
while (scanf("%d %" PRIu32, &n, &key) == 2) {
uint32_t sum = 0;
for (int i = 1; i <= n; i++) {
sum += encrypt(i, key);
prefix_sum[i] = sum;
}
output(prefix_sum, n);
}
return 0;
}

secret.c (測試用)

實際情況會用助教寫好的平行方式進行計算且雜湊公式不同。

1
2
3
4
5
6
7
8
9
10
11
12
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <inttypes.h>
#include "utils.h"
void output(uint32_t presum[], int n) {
uint32_t hash = 0;
for (int i = 1; i <= n; i++)
hash += presum[i] * i;
printf("%" PRIu32 "\n", hash);
}

輸入格式

輸入有多組測資,每組測資一行兩個整數 $n$, $\textit{key}$,分別為序列長度 $n$,加密金鑰 $\textit{key}$

  • $1 \le n \le 10^7$
  • $0 \le key < 2^{32}$

輸出格式

對於每一組測資呼叫 output(uint32_t presum[], int n),隨後會輸出一個數值。

範例輸入

1
2
3 2
10 5

範例輸出

1
2
100
54560

範例解釋

  • $(n, \textit{key})=(3, 2)$,得 $A \left[ 1 \cdots 3\right] = \left[ 4, 8, 12 \right]$$S \left[ 1 \cdots 3\right] = \left[ 4, 12, 24 \right]$$\text{hash} = 4 + 12 \times 2 + 24 \times 3 = 100$

編譯方式

1
gcc -std=c99 -O2 -pthread prefixsum-seq.c secret.c -o prefixsum-seq

測試主機資訊

推薦使用 4 個執行緒解決此問題,平行效能接近 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
processor : 0
vendor_id : GenuineIntel
cpu family : 6
model : 62
model name : Intel(R) Xeon(R) CPU E5-2620 v2 @ 2.10GHz
stepping : 4
microcode : 0x415
cpu MHz : 1200.000
cache size : 15360 KB
physical id : 0
siblings : 12
core id : 0
cpu cores : 6
apicid : 0
initial apicid : 0
fpu : yes
fpu_exception : yes
cpuid level : 13
wp : yes
processor : 1
vendor_id : GenuineIntel
cpu family : 6
model : 62
model name : Intel(R) Xeon(R) CPU E5-2620 v2 @ 2.10GHz
stepping : 4
microcode : 0x415
cpu MHz : 1200.000
cache size : 15360 KB
physical id : 0
siblings : 12
core id : 1
cpu cores : 6
apicid : 2
initial apicid : 2
fpu : yes
fpu_exception : yes
cpuid level : 13
wp : yes

Solution

利用 $P$ 的 thread,分開計算區間 $[1, N/P], \; [N/P+1, 2N/P], \cdots$ 的前綴和,由於前綴和只有利用加法計算,加法具有結合律,隨後傳遞前 $i$ 段的和,可以循序 $\mathcal{O}(P)$ 完成,或者利用樹形平行 (tree-based) 的方式在 $\mathcal{O}(\log P)$,在實務上直接循序即可。

由於採用分治方式,需要平行兩次,時間複雜度為 $\mathcal{O}(2 \frac{N}{P} + P)$

  • $P = 3$ 時,只獲得 1.5 倍的加速
  • $P = 4$ 時,獲得 2 倍加速
  • $P > 4$ 時,要將記憶體的部分傳到另一顆 CPU 上,假設搬運時間是線性正比,計算複雜度也是線性時間,那麼搬運時間不如在同一顆 CPU 上運行。
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
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <inttypes.h>
#include <pthread.h>
#include "utils.h"
typedef struct Argu {
int l, r;
uint32_t *psum;
uint32_t key;
} Argu;
int min(int x, int y) {
return x < y ? x : y;
}
void* subtask1(void *argu) {
Argu data = *((Argu *) argu);
int l = data.l, r = data.r;
uint32_t *psum = data.psum;
uint32_t sum = 0, key = data.key;
for (int i = l, j = 0; i <= r; i++, j++) {
sum += encrypt(i, key);
psum[j] = sum;
}
free(argu);
}
void* subtask2(void *argu) {
Argu data = *((Argu *) argu);
int l = data.l, r = data.r;
uint32_t *psum = data.psum;
uint32_t base = data.key;
for (int i = l, j = 0; i <= r; i++, j++)
psum[j] += base;
free(argu);
}
#define MAXN 10000005
#define MAX_THREAD 4
uint32_t prefix_sum[MAXN];
int main() {
int n;
uint32_t key;
while (scanf("%d %" PRIu32, &n, &key) == 2) {
int BLOCK = (n+MAX_THREAD-1) / MAX_THREAD, m = 0;
pthread_t threads[MAX_THREAD];
for (int i = 1; i <= n; ) {
Argu *argu = (Argu *) malloc(sizeof(Argu));
argu->l = i, argu->r = min(n, i + BLOCK - 1);
argu->psum = prefix_sum + i, argu->key = key;
i += BLOCK;
pthread_create(&threads[m], NULL, subtask1, argu), m++;
}
for (int i = 0; i < m; i++)
pthread_join(threads[i], NULL);
m = 0;
uint32_t block_sum = 0;
for (int i = 1; i <= n; i) {
uint32_t tmp = block_sum;
block_sum += prefix_sum[min(n, i+BLOCK-1)];
Argu *argu = (Argu *) malloc(sizeof(Argu));
argu->l = i, argu->r = min(n, i + BLOCK - 1);
argu->psum = prefix_sum + i, argu->key = tmp;
i += BLOCK;
pthread_create(&threads[m], NULL, subtask2, argu), m++;
}
for (int i = 0; i < m; i++)
pthread_join(threads[i], NULL);
output(prefix_sum, n);
}
return 0;
}
Read More +

UVa 13006 - Cake cut

Problem

兩個人均分一個簡單凸多邊形的蛋糕,蛋糕高度為 2。第一個人 $A$ 選擇一個頂點 $u$,第二個人 $B$ 選擇另一個頂點 $v$,兩點連線後,將蛋糕分成兩塊,最大那一塊會被 $A$ 拿走,小的那一塊會被 $B$ 拿走,請問在各自最佳策略下,盡可能拿到最大塊面積的結果為何?

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
5
0 0
3 0
3 1
2 2
0 1
6
0 1
1 0
2 0
3 1
2 2
0 2
4
-100000000 -100000000
100000000 -100000000
100000000 100000000
-100000000 100000000
4
-99999995 -100000000
100000000 -100000000
100000000 99999995
-100000000 100000000

Sample Output

1
2
3
4
7 2
6 3
40000000000000000 40000000000000000
39999999999999975 39999998000000025

Solution

首先,在最佳策略下,當 $u$ 已經固定,接下來選擇頂點 $v$$B$ 的決定必然要使最小塊最大化,而對於 $A$ 而言,選擇 $B$ 已經使最小塊最大化的所有方法中,使得最大塊最大化。

明顯地兩塊面積近似一半,由於是凸多邊形,可以根據掃描線的方式推動頂點 $v$,使得 $\overline{uv}$ 切分的面積近似一半,對於簡單多邊形面積計算由行列式計算,但行列式計算時的變數呈現環狀,先把首尾變數提出,維護中行列式中間一大段的計算,最後補足首尾變數來計算面積。

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
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <string.h>
#include <set>
#include <limits.h>
#include <algorithm>
using namespace std;
struct Pt {
long long x, y;
Pt(long long a = 0, long long b = 0):
x(a), y(b) {}
Pt operator-(const Pt &a) const {
return Pt(x - a.x, y - a.y);
}
Pt operator+(const Pt &a) const {
return Pt(x + a.x, y + a.y);
}
Pt operator*(const double a) const {
return Pt(x * a, y * a);
}
};
long long cross(Pt o, Pt a, Pt b) {
return (a.x-o.x)*(b.y-o.y)-(a.y-o.y)*(b.x-o.x);
}
long long area2(Pt o, Pt a, Pt b) {
return llabs(cross(o, a, b));
}
const int MAXN = 262144;
Pt P[MAXN];
int main() {
int N;
while (scanf("%d", &N) == 1) {
for (int i = 0; i < N; i++) {
scanf("%lld %lld", &P[i].x, &P[i].y);
P[i+N] = P[i];
}
long long area = 0;
for (int i = 2; i < N; i++)
area += area2(P[0], P[i-1], P[i]);
long long tmp = 0;
pair<long long, long long> ret(0, 0);
tmp += P[0].x * P[1].y - P[0].y * P[1].x;
tmp += P[1].x * P[2].y - P[1].y * P[2].x;
for (int i = 0, j = 2; i < N; i++) {
while (1) {
long long test = tmp + (P[j].x * P[i].y - P[j].y * P[i].x);
if (llabs(test)*2 >= area)
break;
tmp += P[j].x * P[j+1].y - P[j].y * P[j+1].x;
j++;
}
long long t1, t2, p1, p2;
t1 = llabs(tmp + (P[j].x * P[i].y - P[j].y * P[i].x));
t2 = area - t1;
p1 = llabs(tmp - (P[j-1].x * P[j].y - P[j-1].y * P[j].x)
+ (P[j-1].x * P[i].y - P[j-1].y * P[i].x));
p2 = area - p1;
if (t1 < t2) swap(t1, t2);
if (p1 < p2) swap(p1, p2);
if (t1 > p1) t1 = p1, t2 = p2;
ret = max(ret, make_pair(t1, t2));
tmp -= P[i].x * P[i+1].y - P[i].y * P[i+1].x;
}
printf("%lld %lld\n", ret.first, ret.second);
}
return 0;
}
Read More +

UVa 13010 - Galactic taxes

Problem

ACM 辦公室在銀河的各處,辦公室編號 $1$$N$,辦公室 $i$ 和辦公室 $j$ 之間的移動費用隨著時間 $t$ 成線性關係,假設移動不消耗時間,請決定移動時間 $t$,從辦公室 $1$ 移動到辦公室 $N$ 請問最少花費為何。

給定時間必須在一天以內完成,意即 $0 \le t \le 24 \times 60$

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
2 1
1 2 1 0
5 8
1 2 27 610658
2 3 -48 529553
3 4 -6 174696
4 5 47 158238
3 5 84 460166
1 3 -21 74502
2 4 -13 858673
1 5 -90 473410
3 3
1 2 1 0
2 3 1 0
1 3 -1 1440
4 5
1 2 1 0
2 4 2 0
1 4 0 500
1 3 -1 1440
3 4 -2 2880
2 1
1 2 0 0

Sample Output

1
2
3
4
5
1440.00000
419431.27273
960.00000
500.00000
0.00000

Solution

明顯地,每一條邊花費都呈現線性關係,假設一張圖只有一條邊,依序加入新的邊進去,時間 $t$ 對應的路徑花費呈現凹性,因此三分搜尋時間 $t$,做一次 Dijkstra $\mathcal{O}(V \log E)$。由於需要精準度到小數五位,估計要到至少三分 50 次,總時間複雜度 $\mathcal{O}(100 V \log E)$

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
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <string.h>
#include <set>
#include <algorithm>
using namespace std;
const int MAXV = 2048;
const int MAXE = 131072;
const long long INF = 1e+60;
struct Edge {
int to, eid;
double w;
Edge *next;
};
Edge edge[MAXE], *adj[MAXV];
int e = 0;
double dist[MAXV];
void addEdge(int x, int y, double v) {
edge[e].to = y, edge[e].w = v, edge[e].eid = e;
edge[e].next = adj[x], adj[x] = &edge[e++];
}
void dijkstra(int st, double dist[], int n) {
typedef pair<double, int> PLL;
for (int i = 0; i <= n; i++)
dist[i] = INF;
set<PLL> pQ;
PLL u;
pQ.insert(PLL(0, st)), dist[st] = 0;
while (!pQ.empty()) {
u = *pQ.begin(), pQ.erase(pQ.begin());
for (Edge *p = adj[u.second]; p; p = p->next) {
if (dist[p->to] > dist[u.second] + p->w) {
if (dist[p->to] != INF)
pQ.erase(pQ.find(PLL(dist[p->to], p->to)));
dist[p->to] = dist[u.second] + p->w;
pQ.insert(PLL(dist[p->to], p->to));
}
}
}
}
int I[MAXE], J[MAXE], A[MAXE], B[MAXE];
double f(int N, int M, double t) {
e = 0;
for (int i = 1; i <= N; i++)
adj[i] = NULL;
for (int i = 0; i < M; i++) {
double cost = t * A[i] + B[i];
addEdge(I[i], J[i], cost);
addEdge(J[i], I[i], cost);
}
dijkstra(1, dist, N);
return dist[N];
}
int main() {
int N, M;
while (scanf("%d %d", &N, &M) == 2) {
for (int i = 0; i < M; i++)
scanf("%d %d %d %d", &I[i], &J[i], &A[i], &B[i]);
double l = 0, r = 24 * 60, mid, midmid, md, mmd;
double ret = 0;
for (int it = 0; it < 100; it++) {
mid = (l+r)/2;
midmid = (mid+r)/2;
md = f(N, M, mid);
mmd = f(N, M, midmid);
ret = max(ret, md);
if (fabs(md - mmd) < 1e-6)
break;
if (md < mmd)
l = mid;
else
r = midmid;
}
printf("%.5lf\n", ret);
}
return 0;
}
Read More +

UVa 13014 - Keep it energized

Problem

$N$ 道關卡,每一道關卡需要消耗能量 $E_i$ 才能通過,每一個關卡有能量商店,只能進行一次交易,一旦交易成功,不管先前的剩餘能量為何,直接消耗 $C_j$ 元補能量到 $S_j$,請問至少花費多少錢才能闖完所有關卡。

Sample Input

1
2
3
4
5
6
7
8
9
10
11
12
5 4
1 2 3 4 5
1 6 5
2 14 10
5 5 4
3 7 5
3 4
14 11 2015
1 14 23
2 11 9
3 1987 1
1 2039 33

Sample Output

1
2
14
-1

Solution

明顯地,維護一個最小堆,每一個元素紀錄 <在哪一關買商店, 累計多少花費, 購買時有多少能量>,隨著掃描線移動,依序出隊那些無法抵達的元素,並且隊首花費就是到這一關的最少花費 $C$,再依序嘗試在新的商店購買能量,累計花費後入隊。

時間複雜度 $\mathcal{O}(N \log M)$

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 <stdlib.h>
#include <set>
#include <vector>
#include <algorithm>
#include <math.h>
#include <time.h>
using namespace std;
const int MAXN = 131072;
int E[MAXN];
long long sumE[MAXN];
vector< pair<int, int> > shop[MAXN];
struct ELE {
long long cost;
int x, e;
ELE(long long cost = 0, int x = 0, int e = 0):
cost(cost), x(x), e(e) {}
bool operator<(const ELE &v) const {
if (cost != v.cost)
return cost < v.cost;
if (x != v.x)
return x < v.x;
return e < v.e;
}
};
int main() {
int N, M;
while (scanf("%d %d", &N, &M) == 2) {
for (int i = 1; i <= N; i++)
scanf("%d", &E[i]), shop[i].clear();
for (int i = 0; i < M; i++) {
int L, S, C;
scanf("%d %d %d", &L, &S, &C);
shop[L].push_back(make_pair(S, C));
}
for (int i = 1; i <= N; i++)
sumE[i] = sumE[i-1] + E[i];
set<ELE> S;
for (int i = 1; i <= N; i++) {
while (!S.empty()) {
ELE u = *S.begin();
if (u.e < sumE[i-1] - sumE[u.x-1])
S.erase(S.begin());
else
break;
}
if (S.empty() && i != 1) {
} else {
long long mm = i == 1 ? 0 : S.begin()->cost;
for (int j = 0; j < shop[i].size(); j++) {
pair<int, int> p = shop[i][j];
if (p.first >= E[i])
S.insert(ELE(mm + p.second, i, p.first));
}
}
}
while (!S.empty()) {
ELE u = *S.begin();
if (u.e < sumE[N] - sumE[u.x-1])
S.erase(S.begin());
else
break;
}
if (S.empty())
puts("-1");
else
printf("%lld\n", S.begin()->cost);
}
return 0;
}
Read More +

UVa 13054 - Hippo Circus

Problem

馬戲團河馬過門,單一隻河馬過門需要時間 $T_a$,兩隻河馬疊在一起且滿足總高度小於 $H$,需要花費 $T_b$ 的時間。

不管任何順序過門,請問最少時間為何?

Sample Input

1
2
3
4
5
2
3 5 2 3
3 4 2
3 6 2 3
3 4 2

Sample Output

1
2
Case 1: 6
Case 2: 5

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
#include <bits/stdc++.h>
using namespace std;
int A[131072], used[131072];
int main() {
int testcase, cases = 0;
scanf("%d", &testcase);
while (testcase--) {
int N, H, Ta, Tb;
scanf("%d %d %d %d", &N, &H, &Ta, &Tb);
for (int i = 0; i < N; i++)
scanf("%d", &A[i]);
sort(A, A+N);
long long ret = 0;
memset(used, 0, sizeof(used));
int r = N-1;
for (int i = 0; i < N; i++) {
if (used[i] == 1) continue;
while (r > i && (A[i] + A[r] >= H || used[r] == 1))
r--;
if (r > i && A[i] + A[r] < H && used[r] == 0 && Ta+Ta > Tb) {
ret += Tb;
used[r] = used[i] = 1;
} else {
ret += Ta;
used[i] = 1;
}
}
printf("Case %d: %lld\n", ++cases, ret);
}
return 0;
}
Read More +