資訊安全 小考 2 筆記

筆記錯誤多多,請以僅供參考的角度去質疑

Problem

  1. Below is a key distribution protocol.
    (a) Please describe the details of “replay attack” when $N_1$ is removed from the protocol.
    (b) Please modify the protocol to enable party A to confirm that party B already received $K_s$?

  2. Please explain link encryption and end-to-end encryption.

  3. (a) Please describe details of the Fermat’s theorem (or called Fermat’s little theorem).
    (b) How to compute the multiplicative inverse of an integer $a$ modulo $p$ based on the Fermat’s theorem?
    (c) What is the requirement for $a^{-1}$ to exist?

  4. (a) Please describe details of the Euler’s theorem.
    (b) Based on this theorem, please describe the basic idea of the RSA encryption and decryption process.

  5. The Miller-Rabin algorithm is used to test whether an odd integer n is a
    prime. Please complete the following missing steps of teh algorithm. (15%)
    (1) Find integers $k, q$ ($k$ > 0, $q$ odd), so that $(n - 1)$ = __________
    (2) Select a random integer $a$, $1 < a < n - 1$
    (3) if (_______) then return (“maybe prime”)
    (4) for $j = 0$ to $k - 1$ do
    (5) if (_________) then return (“maybe prime”)
    (6) return (“composite”)

  6. Please describe details of the Chinese remainder theorem for the case of $n = p \times q$ where both $p$ and $q$ are primes. (Your anseer should include how to transform an ordinary domain integer $A$ to the CRT domain, and the CRT formula used to inverse-transform the CRT domain representation back to the ordinary domain integer).

Notes

Part 1

基礎步驟如下

1
2
3
4
5
(1) A -> KDC : IDA || IDB || N1
(2) KDC -> A : E(Ka, [Ks || IDA || IDB || N1]) || E(Kb, [Ks || IDA])
(3) A -> B : E(Kb, [Ks || IDA])
(4) B -> A : E(Ks, N2)
(5) A -> B : E(Ks, f(N2))

如果沒有 $N_1$,紀錄 IDA || IDB、E(Ka, [Ks || IDA || IDB]) || E(Kb, [Ks || IDA]),就能假冒 KDC 讓 A 用固定的 $K_s$ 去跟 B 連線。再藉由已知明文,就能知道雙方的溝通內容,進行竊聽。

為了讓 A 知道 B 擁有 $K_s$ 修改其中兩個步驟

1
2
modify (3) E(Kb, [Ks || IDA]) || Nx
(4) E(Ks, N2 || Nx)

Part 2

  • link encryption 屬於 router 之間的通訊,屬於低階網路層協定,在每一條連輸線路各自獨立的加解密,會修改訊息 header,可以增加 padding,保護流量偵測。
  • end-to-end encryption 屬於原點、目的地之間的加密,它們共同享有一把 shared key,屬於高階網路層協定,用來保護資料安全,訊息 header 保留給傳輸用。

Part 3

Fermat’s theorem :

$$a^{p-1} \equiv 1 (\text{mod } p)$$

為了要求 $x^{-1}$,則利用費馬小定理 $x^{p-2} \times x \equiv 1 (\text{mod } p)$,因此 $x^{-1} \equiv x^{p-2} (\text{mod }p)$,特別小心 $p$ 一定要是質數,且滿足 $gcd(x, p) = 1$

Part 4

Euler’s theorem

$$a^{\phi(n)} \equiv 1 (\text{mod } n) \text{ , }gcd(a, n) = 1$$

先來複習一下 RSA 算法的產生與加解密

  1. select two large primes $p$ and $q$ at random.
  2. Let $N = p \times q$, $\phi(N) = (p-1)(q-1)$
  3. select (at random or fixed) the encryption key $e$
    where $1 < e < \phi(N)$, $gcd(e,\phi(N))=1$
    so, $e^{-1} \mod \phi(N)$ exists
  4. solve following equation to find decryption key $d = e^{-1}$, $e \times d \mod \phi(N)=1$
  5. publish public/encryption key:$KP = \left \{ e,N \right \}$
  6. keep secret private/decryption key:$KS = \left \{ d, p, q \right \}$

encrypt a message $M$ the sender:

  • obtains public key of recipient $KP = \left \{ e,N \right \}$
  • computes: $C = M^e \mod N$

decryption a ciphertext $C$

  • uses his private key$KS = \left \{ d, p, q \right \}$
  • computes: $M = C^d \mod N$

用來加解密,為什麼會是正確的呢?

$$\begin{align} & M \equiv (M^e)^d \mod N \\ & M \equiv M^{ed} \mod N \\ & \because ed \equiv 1 \mod \phi(N) \text{ and Euler's theorem} \\ & \therefore M \equiv M^{ed \mod \phi(N)} \mod N \\ & M \equiv M^{1} \mod N \end{align}$$

這裡很神祕,假設 $n = 21$,則 $phi(n) = 12$,根據歐拉公式 $3^{12} \equiv 15 \mod 21$ 違反歐拉定理,但是在 ACM-ICPC 競賽中,有一點明白到,$a^i$$\mod n$ 下的餘數循環節長度 L,則滿足 $L | phi(n)$,藉由這一點來正確解密,至於到底算不算利用歐拉定理仍是個有趣問題。

Part 5

1
2
3
(1) 2^k q
(3) a^q == 1 mod n
(5) a^(2^j q) == n-1 mod n

Part 6

雖然題目要求得不多,這裡直接拿 RSA 作範例

  1. $N = p \times q$
  2. 目標計算 $M = C^d \mod N$
    分開計算得到兩個部分,這裡可以利用歐拉定理加快,因此 $d$ 可以先 $\mod \phi(p)$

    $Mp = C^d \mod p$ $Mq = C^d \mod q$
  3. 套用中國餘式定理 CRT,簡單的模型如下:

    $a_i = M \mod m_i$ $M = a_1 c_1 + a_2 c_2 + \text{ ... }$ $Mi = m_1 \times m_2 ... \times m_n / m_i$ $c_i = M_i \times (M_i^{-1}) \mod m_i$
  4. 因此 RSA 可以預先處理 $c_p = q \times (q^{-1} \mod p)$$c_q = p \times (p^{-1} \mod q)$,還原的算法則是 $M = Mp \times c_p + Mq \times c_q \mod N$

結論,由於拆分後的 bit length 少一半,乘法速度快 4 倍,快速冪次方快 2 倍 (次方的 bit length 少一半),但是要算 2 次,最後共計快 4 倍。CPU 的乘法想必不會用快速傅立葉 FFT 來達到乘法速度為 $O(n \log n)$

但是利用 CRT 計算容易受到硬體上攻擊,因為會造成 $p, q$ 在分解過程中獨立出現,當初利用 $N$ 很難被分解的特性來達到資訊安全,但是卻因為加速把 $p, q$ 存放道不同時刻的暫存器中。

其中一種攻擊,計算得到 $M = Mp \times q \times (q^{-1} \mod p) + Mq \times p \times (p^{-1} \mod q) \mod N$ 當擾亂後面的式子 (提供不穩定的計算結果)。得到 $M' = Mp \times q \times (q^{-1} \mod p) + (Mq + \Delta) \times p \times (p^{-1} \mod q) \mod N$

接著 $(M' - M) = (\Delta' \times p) \mod N$,若要求 $p$ 的方法為 $gcd(M' - M, N) = gcd(\Delta' \times p, N) = p$,輾轉相除的概念呼之欲出,原來 $p$ 會被這麼夾出,當得到兩個 $p, q$,RSA 算法就會被破解。

Read More +

ITSA 2015 第四屆 桂冠賽

中文題目,那就不多做解釋,咱們直接來坑題解。由於我沒有參與比賽,目前也沒辦法進行 submit 測試我的代碼跟算法正確性。因此以下內容、代碼僅供參考。

題目已經放在 E-tutor,但沒提供測試功能,不能 submit 的 OJ 相當逗趣,再等幾天吧,也許在調數據的時間,或者是根本不打算弄好 … 也許是下一次舉辦比賽才完成。

看過一次題目,大約有下列特徵算法 模擬、Bfs、樹形 dp、拓樸排序、最短路徑、dp、二分圖最大匹配、搜索優化、矩形并、掃描線、二分答案。有一題沒看懂,對於那題多維度部分,可能需要的是假解搜索。

有提供中文題目描述呢,不確定自己是否都看得懂,當然程式有點 bug 的話,歡迎大家來 challenge。

感謝各位提供的解法!讓我更了解 BWT $O(n)$ 逆變換。

Problem A

每一輪進行投票,將少數票的那一方留下,直接模擬運行即可,UVa 也有類似的題目。

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
#include <stdio.h>
#include <vector>
using namespace std;
int A[1024][512];
char s[16];
int main() {
int testcase;
int n, m;
scanf("%d", &testcase);
while (testcase--) {
scanf("%d %d", &n, &m);
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
scanf("%s", s);
A[i][j] = s[0] == 'Y';
}
}
vector<int> p;
for (int i = 0; i < n; i++)
p.push_back(i);
for (int i = 0; i < m; i++) {
int Y = 0;
for (int j = 0; j < p.size(); j++)
Y += A[p[j]][i] == 1;
if (Y == p.size() - Y || Y == 0 || Y == p.size())
continue;
vector<int> np;
for (int j = 0; j < p.size(); j++) {
if (Y < p.size() - Y) {
if (A[p[j]][i] == 1)
np.push_back(p[j]);
} else {
if (A[p[j]][i] == 0)
np.push_back(p[j]);
}
}
p = np;
}
for (int i = 0; i < p.size(); i++) {
printf("%d%c", p[i]+1, i == p.size()-1 ? '\n' : ' ');
}
}
return 0;
}

Problem B

兩個機器人運行的行動和週期不同,用 timeline 的方式去模擬兩台機器人的狀態,利用 time mod (N + E) 來得到當前屬於前 N 還是後 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
#include <stdio.h>
#include <vector>
using namespace std;
int main() {
int testcase;
int N, M, N1, F1, N2, F2;
int X1, Y1, E1, X2, Y2, E2;
scanf("%d", &testcase);
while (testcase--) {
scanf("%d %d", &M, &N);
scanf("%d %d %d %d %d", &X1, &Y1, &E1, &N1, &F1);
scanf("%d %d %d %d %d", &X2, &Y2, &E2, &N2, &F2);
int has = 0;
for (int i = 0; i <= max(F1, F2); i++) {
if (X1 == X2 && Y1 == Y2) {
printf("robots explode at time %d\n", i);
has = 1;
break;
}
if (i < F1) {
if (i%(N1 + E1) < N1)
Y1 = (Y1 + 1)%N;
else
X1 = (X1 + 1)%M;
}
if (i < F2) {
if (i%(N2 + E2) < E2)
X2 = (X2 + 1)%M;
else
Y2 = (Y2 + 1)%N;
}
}
if (!has)
puts("robots will not explode");
}
return 0;
}

Problem C

樹中心 (把一點當根,到所有葉節點距離最大值越小越好),其中一種方法是使用樹形 dp,另外一種方法是拓樸排序。保證樹中心會在最長路徑上的中點,當最長路徑是偶數個頂點構成,則中心會有兩個代表,反之會有一個。只會有這兩種情況。

那麼拓樸排序拔點,順便紀錄拓樸的深度 (分層去看),看最後一層有一個點、還是兩個點,找字典順序最小的。

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
#include <stdio.h>
#include <vector>
#include <queue>
#include <algorithm>
using namespace std;
const int MAXN = 32767;
vector<int> g[MAXN];
int deg[MAXN], dist[MAXN];
int main() {
int testcase, n, u, v;
scanf("%d", &testcase);
while (testcase--) {
scanf("%d", &n);
for (int i = 0; i < n; i++)
g[i].clear(), deg[i] = 0, dist[i] = -1;
for (int i = 1; i < n; i++) {
scanf("%d %d", &u, &v);
g[u].push_back(v);
g[v].push_back(u);
deg[u]++, deg[v]++;
}
queue<int> Q;
for (int i = 0; i < n; i++)
if (deg[i] == 1)
Q.push(i), dist[i] = 1;
while (!Q.empty()) {
u = Q.front(), Q.pop();
for (int i = 0; i < g[u].size(); i++) {
v = g[u][i];
if (--deg[v] == 1) {
dist[v] = dist[u] + 1;
Q.push(v);
}
}
}
int mx = 0, ret;
for (int i = 0; i < n; i++) {
if (dist[i] > mx)
mx = dist[i], ret = i;
}
printf("%d\n", ret);
}
return 0;
}

Problem D

Bfs 搜索,定義狀態為 dist[x][y][dir] 表示從起點到 (x, y) 時,利用 dir 方向上的門進入的最短路徑,接著按照 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
#include <stdio.h>
#include <queue>
#include <algorithm>
#include <string.h>
using namespace std;
const int MAXN = 20;
const int dx[] = {0, 1, 0, -1};
const int dy[] = {1, 0, -1, 0};
char sg[MAXN][MAXN][4][8];
int dist[MAXN][MAXN][4];
int toDir(char c) {
switch(c) {
case 'E': return 0;
case 'S': return 1;
case 'W': return 2;
case 'N': return 3;
}
return -1;
}
void bfs(int dir, int sx, int sy, int ex, int ey) {
queue<int> X, Y, D;
memset(dist, 0, sizeof(dist));
X.push(sx), Y.push(sy), D.push(dir);
dist[sx][sy][dir] = 1;
while (!X.empty()) {
sx = X.front(), X.pop();
sy = Y.front(), Y.pop();
dir = D.front(), D.pop();
if (sx == ex && sy == ey) {
printf("%d\n", dist[sx][sy][dir]);
return ;
}
for (int i = 0; sg[sx][sy][dir][i]; i++) {
int d = toDir(sg[sx][sy][dir][i]);
int tx, ty, td;
tx = sx + dx[d], ty = sy + dy[d];
td = (d + 2)%4;
if (dist[tx][ty][td] == 0) {
dist[tx][ty][td] = dist[sx][sy][dir]+1;
X.push(tx), Y.push(ty), D.push(td);
}
}
}
puts("Are you kidding me?");
}
int main() {
int testcase;
int n, m, q, x, y;
scanf("%d", &testcase);
while (testcase--) {
scanf("%d %d", &n, &m);
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
scanf("%d %d", &x, &y);
for (int k = 0; k < 4; k++)
scanf("%s", &sg[x][y][k]);
}
}
scanf("%d", &q);
for (int i = 0; i < q; i++) {
int ex, ey, sx, sy;
char s[8];
scanf("%s %d %d %d %d", &s, &sx, &sy, &ex, &ey);
bfs(toDir(s[0]), sx, sy, ex, ey);
}
puts("----------");
}
return 0;
}

Problem E

題目沒看懂,一扯到多維度計算,似乎每年都是同一個出題者嗎?有一種既視感。

Problem F

可以參照 Zerojudge 基因的核,找到多字串的 LCS 長度。但是這一題要求找到所有 LCS,而且還要按照字典順序排好,同時也不能輸出重複的。根據討論,輸出結果可能破百萬,那麼從輸出檔案中得知,這有點不科學大小,光是輸出有可能就會 TLE。

下方的代碼也許只有長度輸出是正確的,在輸出解部分有點問題,假設答案總數並不多,為了加速排序部分以及重複回溯,使用 trie 來輔助完成。若答案總數過多,會使得 trie 記憶體滿溢。

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
#include <stdio.h>
#include <assert.h>
#include <string.h>
#include <set>
#include <vector>
#include <algorithm>
#include <iostream>
using namespace std;
const int MAXP = 1<<21;
const int MAXN = 4;
const int MAXL = 64;
char pwd[MAXN][MAXL];
int pwdLen[MAXN], A[MAXN], n, sumA;
char dp[MAXP];
int arg_dp[MAXP][2];
void dfs(int dep, int idx[], int v) {
if (dep == n) {
int same = 1;
for (int i = 1; i < n; i++)
same &= pwd[i][idx[i]] == pwd[0][idx[0]];
if (same) {
int s = 0;
arg_dp[v][0] = -1, arg_dp[v][1] = pwd[0][idx[0]];
for (int i = 0; i < n; i++) {
if (idx[i] == 0) {
dp[v] = 1;
return ;
}
s = s + (idx[i]-1) * A[i];
}
dp[v] = dp[s] + 1, arg_dp[v][0] = s;
} else {
arg_dp[v][0] = -2;
for (int i = 0; i < n; i++) {
if (idx[i] == 0)
continue;
if (dp[v - A[i]] > dp[v])
dp[v] = dp[v - A[i]];
}
}
return ;
}
for (int i = 0; i < pwdLen[dep]; i++) {
idx[dep] = i;
dfs(dep+1, idx, v + A[dep] * i);
}
}
//
#define MAXCHAR (52 + 10)
#define MAXNODE (131072)
class Trie {
public:
struct Node {
Node *next[MAXCHAR];
int cnt;
set<int> S;
void init() {
cnt = 0, S.clear();
memset(next, 0, sizeof(next));
}
} nodes[MAXNODE];
Node *root;
int size, cases;
Node* getNode() {
assert(size < MAXNODE);
Node *p = &nodes[size++];
p->init();
return p;
}
void init() {
size = cases = 0;
root = getNode();
}
inline int toIndex(char c) {
if (c <= '9') return c - '0';
if (c <= 'Z') return 10 + c - 'A';
if (c <= 'z') return 36 + c - 'a';
assert(false);
}
inline int toChar(int i) {
if (i < 10) return i + '0';
if (i < 36) return i - 10 + 'A';
if (i < 62) return i - 36 + 'a';
assert(false);
}
// basis operation
void insert(const char str[], int w) {
Node *p = root;
for (int i = 0, idx; str[i]; i++) {
idx = toIndex(str[i]);
if (p->next[idx] == NULL)
p->next[idx] = getNode();
p = p->next[idx];
}
p->cnt += w;
}
int find(const char str[]) {
Node *p = root;
for (int i = 0, idx; str[i]; i++) {
idx = toIndex(str[i]);
if (p->next[idx] == NULL)
p->next[idx] = getNode();
p = p->next[idx];
}
return p->cnt;
}
void free() {
return ;
}
} tool;
char s[MAXL];
void dfsLCS(int idx[], int v, Trie::Node *u) {
if (v < 0)
return ;
if (arg_dp[v][0] >= -1) {
int vidx = tool.toIndex(arg_dp[v][1]);
if (u->next[vidx] == NULL)
u->next[vidx] = tool.getNode();
u = u->next[vidx];
if (u->S.count(v))
return;
u->S.insert(v);
if (dp[v] == 1)
return ;
if (arg_dp[v][0] != -1) {
for (int i = 0; i < n; i++)
idx[i]--;
dfsLCS(idx, arg_dp[v][0], u);
for (int i = 0; i < n; i++)
idx[i]++;
}
} else {
if (u->S.count(v))
return;
u->S.insert(v);
for (int i = 0; i < n; i++) {
if (idx[i] == 0)
continue;
if (dp[v - A[i]] == dp[v]) {
idx[i]--;
dfsLCS(idx, v - A[i], u);
idx[i]++;
}
}
}
}
void printLCS(int dep, Trie::Node *u, char *s, vector<string> &ret) {
if (u == NULL) return;
if (dep == -1) {
ret.push_back(s+1);
return;
}
for (int i = 0; i < MAXCHAR; i++) {
*s = tool.toChar(i);
printLCS(dep-1, u->next[i], s-1, ret);
}
}
int countLCS(int dep, Trie::Node *u, char *s) {
if (u == NULL) return 0;
if (dep == -1)
return 1;
int ret = 0;
for (int i = 0; i < MAXCHAR; i++) {
*s = tool.toChar(i);
ret += countLCS(dep-1, u->next[i], s-1);
}
return ret;
}
int main() {
int testcase;
scanf("%d", &testcase);
while (testcase--) {
scanf("%d", &n);
for (int i = 0; i < n; i++) {
scanf("%s", pwd[i]);
pwdLen[i] = strlen(pwd[i]);
}
A[0] = 1;
for (int i = 1; i < n; i++)
A[i] = A[i-1] * pwdLen[i-1];
memset(dp, 0, sizeof(char) * A[n-1] * pwdLen[n-1]);
int idx[MAXN], f = A[n-1] * pwdLen[n-1] - 1;
dfs(0, idx, 0);
// printf("%d\n", (int) dp[f]);
tool.init();
memset(s, 0, sizeof(s));
for (int i = 0; i < n; i++)
idx[i] = pwdLen[i]-1;
dfsLCS(idx, f, tool.root);
printf("%d\n", countLCS(dp[f]-1, tool.root, s + dp[f]-1));
vector<string> ret;
printLCS(dp[f]-1, tool.root, s + dp[f]-1, ret);
sort(ret.begin(), ret.end());
for (int i = 0; i < ret.size(); i++)
printf("%s\n", ret[i].c_str());
}
return 0;
}
/*
999
2
abcdabcdabcdabcdefghefghefghefgh
dcbadcbadcbadcbahgfehgfehgfehgfe
2
abcdabcdabcdabcd
dcbadcbadcbadcba
999
2
abcabcaa
acbacba
2
abcdfgh
abccfdsg
3
3124158592654359
3173415926581359
763141578926536359
*/

Problem G

看起來是一題二分圖找最大匹配數,可以利用 maxflow 或者是匈牙利算法得到。

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 <math.h>
#include <algorithm>
#include <queue>
#include <stack>
#include <assert.h>
#include <set>
using namespace std;
const int MAXV = 1024;
const int MAXE = MAXV * 200 * 2;
const int INF = 1<<29;
typedef struct Edge {
int v, cap, flow;
Edge *next, *re;
} Edge;
class MaxFlow {
public:
Edge edge[MAXE], *adj[MAXV], *pre[MAXV], *arc[MAXV];
int e, n, level[MAXV], lvCnt[MAXV], Q[MAXV];
void Init(int x) {
n = x, e = 0;
for (int i = 0; i < n; ++i) adj[i] = NULL;
}
void Addedge(int x, int y, int flow){
edge[e].v = y, edge[e].cap = flow, edge[e].next = adj[x];
edge[e].re = &edge[e+1], adj[x] = &edge[e++];
edge[e].v = x, edge[e].cap = 0, edge[e].next = adj[y];
edge[e].re = &edge[e-1], adj[y] = &edge[e++];
}
void Bfs(int v){
int front = 0, rear = 0, r = 0, dis = 0;
for (int i = 0; i < n; ++i) level[i] = n, lvCnt[i] = 0;
level[v] = 0, ++lvCnt[0];
Q[rear++] = v;
while (front != rear){
if (front == r) ++dis, r = rear;
v = Q[front++];
for (Edge *i = adj[v]; i != NULL; i = i->next) {
int t = i->v;
if (level[t] == n) level[t] = dis, Q[rear++] = t, ++lvCnt[dis];
}
}
}
int Maxflow(int s, int t){
int ret = 0, i, j;
Bfs(t);
for (i = 0; i < n; ++i) pre[i] = NULL, arc[i] = adj[i];
for (i = 0; i < e; ++i) edge[i].flow = edge[i].cap;
i = s;
while (level[s] < n){
while (arc[i] && (level[i] != level[arc[i]->v]+1 || !arc[i]->flow))
arc[i] = arc[i]->next;
if (arc[i]){
j = arc[i]->v;
pre[j] = arc[i];
i = j;
if (i == t){
int update = INF;
for (Edge *p = pre[t]; p != NULL; p = pre[p->re->v])
if (update > p->flow) update = p->flow;
ret += update;
for (Edge *p = pre[t]; p != NULL; p = pre[p->re->v])
p->flow -= update, p->re->flow += update;
i = s;
}
}
else{
int depth = n-1;
for (Edge *p = adj[i]; p != NULL; p = p->next)
if (p->flow && depth > level[p->v]) depth = level[p->v];
if (--lvCnt[level[i]] == 0) return ret;
level[i] = depth+1;
++lvCnt[level[i]];
arc[i] = adj[i];
if (i != s) i = pre[i]->re->v;
}
}
return ret;
}
} g;
int main() {
int testcase;
int n, m;
int nx[128], ny[128], mx[128], my[128];
scanf("%d", &testcase);
while (testcase--) {
scanf("%d %d", &n, &m);
for (int i = 0; i < n; i++)
scanf("%d %d", &nx[i], &ny[i]);
for (int i = 0; i < m; i++)
scanf("%d %d", &mx[i], &my[i]);
int source = n+m, sink = n+m+1;
g.Init(n+m+2);
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
if (abs(nx[i]-mx[j]) + abs(ny[i]-my[j]) <= 5)
g.Addedge(i, j+n, 1);
}
}
for (int i = 0; i < n; i++)
g.Addedge(source, i, 1);
for (int i = 0 ; i < m; i++)
g.Addedge(i+n, sink, 1);
int flow = g.Maxflow(source, sink);
printf("%d\n", flow);
}
return 0;
}

Problem H

如果摩擦力和位能動能互換是連續的,那這一題的作法可能就會有問題。很明顯地為了要求出起點到終點的最少能量需求,必然希望最後到終點的能量恰好為 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
60
61
62
63
64
65
66
#include <stdio.h>
#include <vector>
#include <queue>
#include <algorithm>
using namespace std;
const int MAXN = 8192;
int n, m, st, ed, h[MAXN];
vector< pair<int, int> > g[MAXN];
int dist[MAXN], inq[MAXN];
void spfa(int st, int ed, int n) {
for (int i = 0; i < n; i++)
dist[i] = 0x3f3f3f3f, inq[i] = 0;
queue<int> Q;
int u, v, w;
dist[ed] = 0, Q.push(ed);
while (!Q.empty()) {
u = Q.front(), Q.pop();
inq[u] = 0;
for (int i = 0; i < g[u].size(); i++) {
v = g[u][i].first, w = g[u][i].second;
if (h[v] > h[u]) {
if (dist[v] > max(dist[u] - (h[v] - h[u]) + w, 0)) {
dist[v] = max(dist[u] - (h[v] - h[u]) + w, 0);
if (inq[v] == 0) {
inq[v] = 1;
Q.push(v);
}
}
} else {
if (dist[v] > dist[u] + (h[u] - h[v]) + w) {
dist[v] = dist[u] + (h[u] - h[v]) + w;
if (inq[v] == 0) {
inq[v] = 1;
Q.push(v);
}
}
}
}
}
printf("%d\n", dist[st]);
}
int main() {
int testcase;
int u, v, w;
scanf("%d", &testcase);
while (testcase--) {
scanf("%d %d %d %d", &n, &m, &st, &ed);
st--, ed--;
for (int i = 0; i < n; i++)
g[i].clear();
for (int i = 0; i < n; i++)
scanf("%d", &h[i]);
for (int i = 0; i < m; i++) {
scanf("%d %d %d", &u, &v, &w);
u--, v--;
g[u].push_back(make_pair(v, w));
g[v].push_back(make_pair(u, w));
}
spfa(st, ed, n);
}
return 0;
}

Problem I

快速找到多少對的漢明距離恰好相差 P,保證任兩個字串不同,暴力法是直接 $O(N^2)$ 進行比較。由於字串長度不長,可以考慮一下到底要怎麼優化。這一題突然可以想到 UVa 1462 - Fuzzy Google Suggest,但是那一題考慮到字符串長度會比較長,而且還有編輯距離的搜索,跟漢明距離有點差別,也是值得思考的題目。

由於字串長度為 4,只使用大寫字母和數字,下面測試想法 5 組 50000 個的字串,大約跑了 4 秒,不曉得能不能通過正式比賽的數據。想法很簡單,窮舉相同的位置後,利用排容原理找完全不同的字串數量。

例如給定 ABCD,此時 $P = 2$,那麼找到符合 A_C_ 所有的情況,符合配對的字串保證有相似程度有 2 個,但是這些情況可能會出現 ABC_A_CDABCD,也就是說為了找到符合 A_C___ 不能填入 BD 的任何相似,計算排容得到完全不相似 (有一個相同的位置) 的個數。

Version 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
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 <string.h>
#include <algorithm>
#include <map>
#include <iostream>
using namespace std;
int main() {
int testcase, n, m;
scanf("%d", &testcase);
while (testcase--) {
scanf("%d %d", &n, &m);
map< string, map<string, int> > FUZZY[1<<4];
map< string, int > CNT[1<<4];
int ret = 0;
char s[8], s1[8], s2[8], s3[8];
int same = 4 - m, diff = m;
for (int i = 0; i < n; i++) {
scanf("%s", s);
// find pair
for (int j = 0; j < (1<<4); j++) {
if (__builtin_popcount(j) == same) {
int sidx1 = 0, sidx2 = 0;
for (int k = 0; k < 4; k++) {
if ((j>>k)&1)
s1[sidx1++] = s[k];
else
s2[sidx2++] = s[k];
}
s1[sidx1] = '\0', s2[sidx2] = '\0', s3[sidx2] = '\0';
map<string, int> &tfuzzy = FUZZY[j][s1];
int tot = CNT[j][s1], similar = 0;
if (tot == 0) continue;
for(int k = (1<<diff)-1; k > 0; k--) {
for (int p = 0; p < diff; p++) {
if ((k>>p)&1)
s3[p] = s2[p];
else
s3[p] = '_';
}
// cout << s3 << endl;
if (__builtin_popcount(k)%2 == 0)
similar -= tfuzzy[s3];
else
similar += tfuzzy[s3];
}
ret += tot - similar;
// printf("%d -- %d %d\n", tot - similar, tot, similar);
}
}
// add
for (int j = 0; j < (1<<4); j++) {
if (__builtin_popcount(j) == same) {
int sidx1 = 0, sidx2 = 0, sidx3 = 0;
for (int k = 0; k < 4; k++) {
if ((j>>k)&1)
s1[sidx1++] = s[k];
else
s2[sidx2++] = s[k];
}
s1[sidx1] = '\0', s2[sidx2] = '\0', s3[sidx2] = '\0';
map<string, int> &tfuzzy = FUZZY[j][s1];
CNT[j][s1]++;
for(int k = (1<<diff)-1; k > 0; k--) {
for (int p = 0; p < diff; p++) {
if ((k>>p)&1)
s3[p] = s2[p];
else
s3[p] = '_';
}
tfuzzy[s3]++;
}
}
}
}
printf("%d\n", ret);
}
return 0;
}

Version 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
#include <stdio.h>
#include <string.h>
#include <algorithm>
#include <map>
#include <iostream>
using namespace std;
int toIndex(int x) { // [0...36]
if ('0' <= x && x <= '9')
return x - '0';
if ('A' <= x && x <= 'Z')
return x + 10 - 'A';
return 36;
}
int main() {
int testcase, n, m;
scanf("%d", &testcase);
while (testcase--) {
scanf("%d %d", &n, &m);
map< int, map<int, int> > FUZZY[1<<4];
map< int, int > CNT[1<<4];
int ret = 0;
char s[8], s2[8], s3[8];
int same = 4 - m, diff = m;
for (int i = 0; i < n; i++) {
scanf("%s", s);
// find pair
for (int j = 0; j < (1<<4); j++) {
if (__builtin_popcount(j) == same) {
int sidx1 = 0, sidx2 = 0;
int s1 = 0, s3 = 0;
for (int k = 0; k < 4; k++) {
if ((j>>k)&1)
s1 = s1 * 37 + toIndex(s[k]);
else
s2[sidx2++] = s[k];
}
map<int, int> &tfuzzy = FUZZY[j][s1];
int tot = CNT[j][s1], similar = 0;
CNT[j][s1]++;
for(int k = (1<<diff)-1; k > 0; k--) {
s3 = 0;
for (int p = 0; p < diff; p++) {
if ((k>>p)&1)
s3 = s3 * 37 + toIndex(s2[p]);
else
s3 = s3 * 37 + toIndex('_');
}
if (__builtin_popcount(k)%2 == 0)
similar -= tfuzzy[s3];
else
similar += tfuzzy[s3];
tfuzzy[s3]++;
}
ret += tot - similar;
// printf("%d -- %d %d\n", tot - similar, tot, similar);
}
}
}
printf("%d\n", ret);
}
return 0;
}

Version 3

發現排容地方寫得不恰當,善用排容原理的組合計算,類似 N 不排 N 的組合數 (錯排)。map 查找會慢很多,就算用分堆的 map 效率仍然提升不高,那麼直接開一個陣列空間 $O(37^4) = O(1874161)$,所有字串轉數字,雖然清空會慢很多,但是查找速度夠快。

感謝蚯蚓、卡車告知這問題。

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
#include <stdio.h>
#include <string.h>
#include <algorithm>
#include <map>
#include <iostream>
using namespace std;
inline int toIndex(int x) { // [0...36]
if ('0' <= x && x <= '9')
return x - '0';
if ('A' <= x && x <= 'Z')
return x + 10 - 'A';
return 36;
}
const int MOD = 1874161; // 37^4
int FUZZY[MOD];
int main() {
int C[16][16] = {};
for (int i = 0; i < 16; i++) {
C[i][0] = 1;
for (int j = 1; j <= i; j++)
C[i][j] = C[i-1][j-1] + C[i-1][j];
}
int testcase, n, m;
scanf("%d", &testcase);
while (testcase--) {
scanf("%d %d", &n, &m);
for (int i = 0; i < MOD; i++)
FUZZY[i] = 0;
int ret = 0;
int same = 4 - m, diff = m;
char s[8];
for (int i = 0; i < n; i++) {
scanf("%s", s);
// find pair
for (int j = 0; j < (1<<4); j++) {
if (__builtin_popcount(j) >= same) {
int s1 = 0;
for (int k = 0; k < 4; k++) {
if ((j>>k)&1)
s1 = s1 * 37 + toIndex(s[k]);
else
s1 = s1 * 37 + toIndex('_');
}
int &r = FUZZY[s1];
int v = __builtin_popcount(j);
if ((v - same)%2 == 0)
ret += r * C[v][same];
else
ret -= r * C[v][same];
r++;
}
}
}
printf("%d\n", ret);
}
return 0;
}

Problem J

考了一題 BWT 壓縮的逆轉換,從簡單的思路至少要 $O(N^2 log N)$ 吧?沒想到的是利用特性可以達到 $O(N)$。這裡需要研究一下 為什麼相同字母順序在壓縮和解壓縮會相同 ,百思不解的就是這個地方,若是能解決,就直接利用輔助數組達到 $O(N)$ 逆推上一個元素是什麼,最後輸出一個反向的結果。

這裡解釋一下順序性問題

1
2
3
4
5
6
BANANA ABANAN (1)A----N(9)
ANANAB sort ANABAN view A (2)A----N(10)
NANABA ------> ANANAB --------> (3)A----B(12)
ANABAN BANANA (11)B----A(4)
NABANA NABANA (7)NAB--A(5)
ABANAN NANABA (8)NAN--A(6)

明顯地左側的 A 順序會跟右側的 A 順序相同,原因是 B----A < NAB--A < NAN--A,那麼保證 AB----, ANAB--, ANAN-- 一定也會出現 (根據 $O(N^2 log N)$ 的做法得到,因為他們是循環的!),既然後綴順序已經排序 (B----A, NAB--A, NAN--A),那麼右側中,相同的字母順序,肯定跟左側一樣 (由小到大)。

藉此可以推導下一個字母到底是何物!例如結尾字母是 A(4),那麼前一個一定是 B(11),而 B(11) 對應右側的 B(12)B(12) 的前一個是 A(3)A(3) 對應右側 A(6)

1
2
3
R: B(11) A(3) N(8) A(2) N(7) A(1) <---- inverse result
/ \ / \ / \ / \ / \ /
L: A(4) B(12) A(6) N(10) A(5) N(9)

Version 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
#include <stdio.h>
#include <string.h>
#include <iostream>
#include <algorithm>
using namespace std;
// O(n), BWT compress inverse
const int MAXN = 2048;
char s[MAXN];
int K[MAXN], M[MAXN], C[MAXN], N;
int main() {
int testcase, e_pos;
scanf("%d", &testcase);
while (testcase--) {
scanf("%s %d", s, &e_pos), e_pos--;
string L(s), F(s);
N = L.length();
memset(K, 0, sizeof(K));
memset(M, 0, sizeof(M));
memset(C, 0, sizeof(C));
for (int i = 0; i < N; i++) {
C[i] = K[L[i]];
K[L[i]]++;
}
sort(F.begin(), F.end());
for (int i = 0; i < N; i++) {
if (i == 0 || F[i] != F[i-1])
M[F[i]] = i;
}
string T(N, '?');
for (int i = 0; i < N; i++) {
T[N-1-i] = L[e_pos];
e_pos = C[e_pos] + M[L[e_pos]];
}
puts(T.c_str());
}
return 0;
}
/*
2
CGA
3
NNBAAA
4
*/

Version 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
#include <iostream>
#include <string>
#include <vector>
#include <utility>
#include <algorithm>
static const int MXN = 514514;
int main() {
int t;
std::cin >> t;
while (t--) {
int p;
std::string str;
std::cin >> str >> p;
std::vector<std::pair<char, int>> vec;
for (size_t i = 0; i < str.length(); i++)
vec.push_back(std::make_pair(str[i], i));
std::sort(vec.begin(), vec.end());
std::string ans;
for (int i = p - 1; ans.length() < str.length(); i = vec[i].second)
ans += vec[i].first;
std::cout << ans << std::endl;
}
}

Problem K

二分答案 + 掃描線,來找到是否矩形并的面積等於所求的整個面積。算法的複雜度是 $O(N \log^2{N} )$,如果太慢的話就懇求各位幫忙。

掃描線部分,將每個平行兩軸的矩形保留垂直的邊,水平的邊不影響運算結果。接著按照 X 軸方向由左往右掃描,將矩形左側的垂直邊當作入邊 +1、右側的垂直邊當作出邊 -1,維護 Y 值的區間覆蓋。

假設 Y 可能是實數,需要針對 Y 進行離散化處理,接著懶操作標記,對於線段樹的某個區間,若覆蓋數 cover = 0 則表示該區間無法提供,相反地提供整個區間。

註記 邪惡的 $\text{round}(\sqrt{k} \times c)$ ,不小心看成 $\text{round}(\sqrt{k}) \times 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
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
#include <stdio.h>
#include <string.h>
#include <map>
#include <vector>
#include <math.h>
#include <algorithm>
using namespace std;
const int MAXN = 131072;
class SegmentTree {
public:
struct Node {
int cover; // #cover
int L, R; // value in real line, cover [L, R]
int clen; // cover length
void init(int a = 0, int b = 0, int c = 0, int d = 0) {
cover = a, L = b, R = c, clen = d;
}
} nodes[MAXN];
int Y[MAXN];
void pushDown(int k, int l, int r) {
}
void pushUp(int k, int l, int r) {
if (nodes[k].cover > 0) {
nodes[k].clen = nodes[k].R - nodes[k].L;
} else {
if (l == r)
nodes[k].clen = 0;
else
nodes[k].clen = nodes[k<<1].clen + nodes[k<<1|1].clen;
}
}
void build(int k, int l, int r) {
nodes[k].init(0, Y[l], Y[r+1], 0);
if (l == r)
return ;
int mid = (l + r)/2;
build(k<<1, l, mid);
build(k<<1|1, mid+1, r);
pushUp(k, l, r);
}
// operator, add
void addUpdate(int k, int l, int r, int val) {
nodes[k].cover += val;
if (nodes[k].cover > 0) {
nodes[k].clen = nodes[k].R - nodes[k].L;
} else {
if (l == r)
nodes[k].clen = 0;
else
nodes[k].clen = nodes[k<<1].clen + nodes[k<<1|1].clen;
}
}
void add(int k, int l, int r, int x, int y, int val) {
if (x <= l && r <= y) {
addUpdate(k, l, r, val);
return;
}
pushDown(k, l, r);
int mid = (l + r)/2;
if (x <= mid)
add(k<<1, l, mid, x, y, val);
if (y > mid)
add(k<<1|1, mid+1, r, x, y, val);
pushUp(k, l, r);
}
// query
long long r_sum;
void qinit() {
r_sum = 0;
}
void query(int k, int l, int r, int x, int y) {
if (x <= l && r <= y) {
r_sum += nodes[k].clen;
return ;
}
pushDown(k, l, r);
int mid = (l + r)/2;
if (x <= mid)
query(k<<1, l, mid, x, y);
if (y > mid)
query(k<<1|1, mid+1, r, x, y);
pushUp(k, l, r);
}
} tree;
struct Event {
long long x, yl, yr;
int val;
Event(long long a = 0, long long b = 0, long long c = 0, int d = 0):
x(a), yl(b), yr(c), val(d) {}
bool operator<(const Event &a) const {
return x < a.x;
}
};
const int MAXD = 32767;
int Lx[MAXD], Ly[MAXD], Rx[MAXD], Ry[MAXD];
long long X[MAXD], Y[MAXD];
double K[MAXD];
long long areaCoverAll(int N, int Lx[], int Ly[], int Rx[], int Ry[]) {
vector<Event> events;
vector<int> VY;
map<int, int> RY;
for (int i = 0; i < N; i++) {
int x1, x2, y1, y2;
x1 = Lx[i], x2 = Rx[i];
y1 = Ly[i], y2 = Ry[i];
if (x1 < x2 && y1 < y2) {
events.push_back(Event(x1, y1, y2, 1));
events.push_back(Event(x2, y1, y2, -1));
VY.push_back(y1), VY.push_back(y2);
}
}
sort(VY.begin(), VY.end());
VY.resize(unique(VY.begin(), VY.end()) - VY.begin());
for (int i = 0; i < VY.size(); i++) {
tree.Y[i] = VY[i];
RY[VY[i]] = i;
}
if (VY.size() < 2)
return 0;
tree.build(1, 0, VY.size()-2);
sort(events.begin(), events.end());
long long area = 0, prevX = 0;
for (int i = 0, j; i < events.size(); ) {
if (i > 0) {
tree.qinit();
tree.query(1, 0, VY.size()-2, 0, VY.size()-2);
area += (events[i].x - prevX) * tree.r_sum;
}
j = i;
while (j < events.size() && events[j].x <= events[i].x) {
tree.add(1, 0, VY.size()-2, RY[events[j].yl], RY[events[j].yr] - 1, events[j].val);
j++;
}
prevX = events[i].x;
i = j;
}
return area;
}
int main() {
int testcase, cases = 0;
long long W, H;
int N;
double sqrtK[128];
for (int i = 0; i < 128; i++)
sqrtK[i] = sqrt(i);
scanf("%d", &testcase);
while (testcase--) {
scanf("%lld %lld", &W, &H);
scanf("%d", &N);
for (int i = 0; i < N; i++) {
int x, y, k;
scanf("%d %d %d", &k, &x, &y);
if (k < 1 || k > 100)
return 0;
X[i] = x, Y[i] = y, K[i] = sqrtK[k];
}
if (N < 1 || N > 20000 || W <= 0 || H <= 0 || W > 1e+7 || H > 1e+7)
return 0;
double l = 0.4, r = max(W, H), mid;
int ret = -1;
while (fabs(l - r) > 0.1) {
mid = (l + r)/2;
for (int i = 0; i < N; i++) {
Lx[i] = min(max(round(X[i] - K[i] * mid), 0.0), (double) W);
Rx[i] = min(max(round(X[i] + K[i] * mid), 0.0), (double) W);
Ly[i] = min(max(round(Y[i] - K[i] * mid), 0.0), (double) H);
Ry[i] = min(max(round(Y[i] + K[i] * mid), 0.0), (double) H);
}
long long area = areaCoverAll(N, Lx, Ly, Rx, Ry);
if (area == W*H)
r = mid, ret = ceil(mid*2);
else
l = mid;
}
printf("Case %d: %d\n", ++cases, ret);
}
return 0;
}
/*
9999
9 9
1
1 0 0
1 1
1
4 0 0
2
12 8
3
4 2 2
16 8 4
4 2 6
12 8
3
4 2 2
10 8 4
4 2 6
*/

後記

如有錯誤,多多包涵。

Read More +

UVa 1402 - Robotic Sort

Problem

機器人要排序序列,每一次會將第 i 小元素放置到正確位置,機器人每一次操作都會翻轉一個區間。請問每一步運行時,要翻轉的元素位置分別在哪裡。

Sample Input

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

Sample Output

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

Solution

我想這一題的做法偏向 treap、splay tree 來維護區間反轉。

這裡用 塊狀鏈表 來試試,為了解決元素查找位置,紀錄該元素所在的堆、該堆的哪個位置。再利用 $O(\sqrt{n})$ 的時間去計算實際位置。還好速度沒有卡很緊,勉強能拿到 AC。弄個內存池說不定可以更快一點。但是常常需要刪除跟增加,必須寫額外的記憶體管理部分 (用一個 set 維護)。

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
#include <stdio.h>
#include <math.h>
#include <stack>
#include <assert.h>
#include <vector>
#include <map>
#include <algorithm>
using namespace std;
// Unrolled Linked List
const int MAXPILE = 512;
const int MAXN = 131072;
class UnrolledLinkedList{
public:
struct Node {
int v[MAXPILE], vn;
int rev_label, pid;
Node *next;
Node() {
vn = rev_label = 0;
next = NULL;
}
void relax() {
if (rev_label) {
for(int i = 0, j = vn-1; i < j; i++, j--)
swap(v[i], v[j]);
rev_label = 0;
}
}
};
Node *head;
int PSIZE, pid;
int e_pos[MAXN], e_id[MAXN];
void init() {
free();
head = NULL, PSIZE = MAXPILE /2;
pid = 0;
}
Node* getNode() {
Node* p = new Node();
p->pid = pid++;
return p;
}
void remap(Node *u) {
for (int i = 0; i < u->vn; i++)
e_pos[u->v[i]] = i, e_id[u->v[i]] = u->pid;
}
int find(int e_val) {
int pid = e_id[e_val];
int sum_element = 0;
Node *u, *v;
for (u = head; u != NULL && u->pid != pid; u = u->next)
sum_element += u->vn;
// printf("find %d - %d\n", e_val, pid);
assert(u != NULL);
if (u->rev_label)
return sum_element + u->vn - 1 - e_pos[e_val];
else
return sum_element + e_pos[e_val];
}
void set(int A[], int n) {
init();
Node *u, *v;
head = getNode();
u = head, v = NULL;
PSIZE = min(PSIZE, (int) sqrt(n));
for (int i = 0; i < n; i++) {
if (u->vn == PSIZE) {
u->next = getNode();
v = u, u = u->next;
}
u->v[u->vn++] = A[i];
}
for (u = head; u != NULL; u = u->next)
remap(u);
}
void shrinkList() {
Node *u, *v;
u = head;
for (u = head; u != NULL && u->next != NULL; u = u->next) {
if (u->vn + u->next->vn <= 2 * PSIZE) { // merge
v = u->next;
u->relax(), v->relax();
for (int i = u->vn, j = 0; j < v->vn; i++, j++)
u->v[i] = v->v[j];
u->vn += v->vn;
remap(u);
u->next = v->next;
delete v;
}
}
}
void splitNode(Node *u, int left_size) { // length(left) = v
Node *v = getNode();
u->relax();
v->next = u->next;
u->next = v;
v->vn = u->vn - left_size;
for(int i = left_size, j = 0; i < u->vn; i++, j++)
v->v[j] = u->v[i];
u->vn = left_size;
remap(u), remap(v);
}
void reverse(int l, int r) { // [l, r] = [0, n-1]
Node *lptr, *rptr, *u, *v;
Node *lpre, *rpre, *rnext;
int sum_element = 0;
u = head, v = NULL;
for (u = head, v = NULL; u != NULL; v = u, u = u->next) {
if (sum_element < l && l < sum_element + u->vn)
splitNode(u, l - sum_element); // left[...l-1], right[l...]
if (sum_element <= r && r < sum_element + u->vn)
splitNode(u, r - sum_element + 1);
if (sum_element == l)
lptr = u, lpre = v;
if (sum_element + u->vn - 1 == r)
rptr = u, rpre = v;
sum_element += u->vn;
}
// debug();
rnext = rptr->next;
stack<Node*> stk;
for (u = lptr; u != rnext; u = u->next) {
u->rev_label = !u->rev_label;
stk.push(u);
}
if (lpre == NULL) {
head = stk.top();
u = head, stk.pop();
while (!stk.empty()) {
u->next = stk.top(), stk.pop();
u = u->next;
}
u->next = rnext;
} else {
u = lpre;
while (!stk.empty()) {
u->next = stk.top(), stk.pop();
u = u->next;
}
u->next = rnext;
}
shrinkList();
}
void debug() {
Node *u = head;
while (u != NULL) {
printf("%d : %d, ", u->pid, u->rev_label);
for(int i = 0; i < u->vn; i++)
printf("%d ", u->v[i]);
puts("");
u = u->next;
}
puts("====");
}
void free() {
Node *u = head, *v = NULL;
while(u != NULL) {
v = u, u = u->next;
delete v;
}
}
} g;
int A[MAXN];
int main() {
int n;
while (scanf("%d", &n) == 1 && n) {
vector< pair<int, int> > B;
for (int i = 0; i < n; i++) {
scanf("%d", &A[i]);
B.push_back(make_pair(A[i], i));
}
sort(B.begin(), B.end());
map< pair<int, int>, int > C;
for (int i = 0; i < B.size(); i++)
C[B[i]] = i;
for (int i = 0; i < n; i++)
A[i] = C[make_pair(A[i], i)];
g.set(A, n);
// g.debug();
vector<int> ret;
for (int i = 0; i < n; i++) {
int pos = g.find(i);
g.reverse(i, pos);
ret.push_back(pos);
// g.debug();
}
for (int i = 0; i < n; i++)
printf("%d%c", ret[i] + 1, i == n-1 ? '\n' : ' ');
}
return 0;
}
/*
6
3 4 5 1 6 2
4
3 3 2 1
0
10
5 18 19 12 7 12 0 2 11 9
1
4
19
5 17 8 10 13 18 10 5 3 15 2 19 12 10 2 14 18 0 6
12
15 13 7 14 15 7 12 15 4 10 6 3
15
18 7 5 6 5 5 10 9 2 4 9 10 7 13 19
5
3 4 1 1 3
6
8 0 6 2 6 16
7
17 5 12 1 3 9 13
1
8
10
15 19 17 19 17 18 2 12 0 10
10
5 1 14 6 7 12 15 17 5 11
0
*/
Read More +

UVa 1438 - Asteroids

Problem

兩個行星碰撞,請問質量中心能夠距離最近為何。

兩個行星會呈現凸多面體的形式,給予行星多面體上的頂點。

Sample Input

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
8
0 0 0
0 0 1
0 1 0
0 1 1
1 0 0
1 0 1
1 1 0
1 1 1
5
0 0 5
1 0 6
-1 0 6
0 1 6
0 -1 6

Sample Output

1
0.75

Solution

題目雖然已經給定所有在凸面體上的頂點,應該是要做一次三維凸包。

質心之間能多近,就是兩質心連線的最短距離,連線後不保證線上每一點都在其中一個凸多面體內部。因此找到凸面體的質心後,窮舉每一個表面拉出的平面,求點到面的最短距離。兩個答案相加即可。

複習下三維凸包算法,維護 conflict graph 去玩的那個,參照網路上的模板繞過一圈,代碼還真是相當簡潔有力 (雖然時間跟空間使用上都沒有辦法好計算,基於期望值 …)!利用空間共平面的順逆時針,來維護外部點判定,解決了之前上課中的困擾。

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
#include <stdio.h>
#include <string.h>
#include <math.h>
#include <algorithm>
using namespace std;
const double eps = 1e-8;
const int MAXN = 1024;
int cmpZero(double x) {
if (fabs(x) < eps) return 0;
return x < 0 ? -1 : 1;
}
class ConvexHull3D {
public:
struct Point3D {
double x, y, z;
Point3D(double a = 0, double b = 0, double c = 0):
x(a), y(b), z(c) {}
Point3D operator+(const Point3D &a) const {
return Point3D(x + a.x, y + a.y, z + a.z);
}
Point3D operator-(const Point3D &a) const {
return Point3D(x - a.x, y - a.y, z - a.z);
}
Point3D operator/(double a) const {
return Point3D(x/a, y/a, z/a);
}
Point3D operator*(double a) const {
return Point3D(x*a, y*a, z*a);
}
bool operator!=(const Point3D &a) const {
return cmpZero(x - a.x) || cmpZero(y - a.y) || cmpZero(z - a.z);
}
Point3D cross(const Point3D &a) const { // outer product
return Point3D(y*a.z - z*a.y, z*a.x - x*a.z, x*a.y - y*a.x);
}
double dot(const Point3D &a) const {
return x*a.x + y*a.y + z*a.z;
}
double length() {
return sqrt(x*x+y*y+z*z);
}
void read() {
scanf("%lf %lf %lf", &x, &y, &z);
}
};
struct Face {
int a, b, c; // point3d id
bool flag; // on Convex Hull
};
int n, tri_num;
Point3D pt[MAXN];
Face faces[MAXN*8];
Face* g[MAXN][MAXN];
double tri_area(Point3D a, Point3D b, Point3D c) { // value >= 0
return ((a - c).cross(b - c)).length()/2;
}
double volume(Point3D a, Point3D b, Point3D c, Point3D d) { // directed
return ((b - a).cross(c - a)).dot(d - a)/6;
}
double pt2Face(Point3D a, Point3D b, Point3D c, Point3D p) {
Point3D n = (b - a).cross(c - a);
Point3D t = p - a;
double v = n.dot(t) / n.length();
return fabs(v);
}
double ptWithFace(Point3D &p, Face &f) { // 0: p in f, <0, >0: above or below
Point3D a, b, c;
a = pt[f.b] - pt[f.a];
b = pt[f.c] - pt[f.a];
c = p - pt[f.a];
return (a.cross(b)).dot(c);
}
bool samePlane(Face &s, Face &t) {
Point3D a, b, c;
bool ret = true;
a = pt[s.a], b = pt[s.b], c = pt[s.c];
ret = cmpZero(volume(a, b, c, pt[t.a])) == 0 &&
cmpZero(volume(a, b, c, pt[t.b])) == 0 &&
cmpZero(volume(a, b, c, pt[t.c])) == 0;
return ret;
}
void deal(int a, int b, int p) {
Face *f = g[a][b];
if (f->flag) {
if (cmpZero(ptWithFace(pt[p], *f)) > 0) {
dfs(p, f);
} else {
Face &add = faces[tri_num++];
add.a = b, add.b = a, add.c = p;
add.flag = 1;
g[b][a] = g[a][p] = g[p][b] = &add;
}
}
}
void dfs(int p, Face *f) {
f->flag = 0; // remove this face.
deal(f->b, f->a, p);
deal(f->a, f->c, p);
deal(f->c, f->b, p);
}
int make() {
if (n < 4)
return 0;
// find a tetrahedron
bool ok;
ok = false;
for (int i = 1; i < n; i++) {
if (pt[0] != pt[i]) {
swap(pt[1], pt[i]);
ok = true;
break;
}
}
if (!ok) return 0;
ok = false;
for (int i = 2; i < n; i++) {
if (cmpZero(tri_area(pt[0], pt[1], pt[i]))) {
swap(pt[2], pt[i]);
ok = true;
break;
}
}
if (!ok) return 0;
ok = false;
for (int i = 3; i < n; i++) {
if (cmpZero(volume(pt[0], pt[1], pt[2], pt[i]))) {
swap(pt[3], pt[i]);
ok = true;
break;
}
}
if (!ok) return 0;
tri_num = 0;
for (int i = 0; i < 4; i++) { // init 4 faces.
Face &f = faces[tri_num++];
f.a = (i+1)%4, f.b = (i+2)%4, f.c = (i+3)%4;
f.flag = 1;
if (cmpZero(ptWithFace(pt[i], f)) > 0)
swap(f.b, f.c);
g[f.a][f.b] = g[f.b][f.c] = g[f.c][f.a] = &f;
}
// add point into convex hull
for (int i = 4; i < n; i++) {
for (int j = 0; j < tri_num; j++) {
if (faces[j].flag && cmpZero(ptWithFace(pt[i], faces[j])) > 0) {
dfs(i, &faces[j]);
break;
}
}
}
// remove unused face, trash g[][]
int tri_n = 0;
for (int i = 0; i < tri_num; i++) {
if (faces[i].flag)
faces[tri_n++] = faces[i];
}
tri_num = tri_n;
return 1;
}
double area() { // surface
double ret = 0;
if (n == 3)
return tri_area(pt[0], pt[1], pt[2]);
for (int i = 0; i < tri_num; i++)
ret += tri_area(pt[faces[i].a], pt[faces[i].b], pt[faces[i].c]);
return ret;
}
double volume() {
double ret = 0;
Point3D o(0, 0, 0);
for (int i = 0; i < tri_num; i++)
ret += volume(o, pt[faces[i].a], pt[faces[i].b], pt[faces[i].c]);
return fabs(ret);
}
Point3D getCenter() {
Point3D ret(0, 0, 0), o = pt[faces[0].a], p;
double sum, v;
sum = 0;
for (int i = 0; i < tri_num; i++) {
v = volume(o, pt[faces[i].a], pt[faces[i].b], pt[faces[i].c]);
p = (pt[faces[i].a] + pt[faces[i].b] + pt[faces[i].c] + o)/4.0;
if (cmpZero(v) > 0) {
p = p * v;
ret = ret + p, sum += v;
}
}
ret = ret / sum;
return ret;
}
int getFaceCount() {
int ret = 0;
for (int i = 0; i < tri_num; i++) {
int ok = 1;
for (int j = 0; j < i && ok; j++) {
if (samePlane(faces[i], faces[j]))
ok = 0;
}
if (ok)
ret++;
}
return ret;
}
double getInnerClosestDist(Point3D p) { // p in Conver Hull
double ret = -1;
for (int i = 0; i < tri_num; i++) {
Point3D a, b, c;
a = pt[faces[i].a], b = pt[faces[i].b], c = pt[faces[i].c];
double t = tri_area(a, b, c);
double v = fabs(volume(a, b, c, p));
if (ret < 0 || v*3/t < ret)
ret = v*3/t;
}
return ret;
}
} A, B;
int main() {
while (scanf("%d", &A.n) == 1) {
for (int i = 0; i < A.n; i++)
A.pt[i].read();
scanf("%d", &B.n);
for (int i = 0; i < B.n; i++)
B.pt[i].read();
A.make();
B.make();
double d1, d2;
d1 = A.getInnerClosestDist(A.getCenter());
d2 = B.getInnerClosestDist(B.getCenter());
printf("%lf\n", d1 + d2);
}
return 0;
}
/*
8
0 0 0
0 0 1
0 1 0
0 1 1
1 0 0
1 0 1
1 1 0
1 1 1
5
0 0 5
1 0 6
-1 0 6
0 1 6
0 -1 6
*/
Read More +

UVa 1581 - Pollution Solution

Problem

計算簡單多邊形與半圓的交集面積

Sample Input

1
2
3
4
5
6
7
6 10
-8 2
8 2
8 14
0 14
0 6
-8 14

Sample Output

1
101.576437872

Solution

Version 1

之前針對簡單多邊形三角剖分,然後去求三角形與圓的關係,還要拆分好幾種可能,考慮共線 … 等複雜操作。

由於最麻煩的地方在於圓與線段之間的關係,想到之前使用 Partition Slab Map,針對 x 座標進行切割,那麼簡單多邊形就會是很多的梯形構成,即便是這樣,梯形還要弄成三角形去跟圓做計算。那換個方式想,直接極角剖分,保證相鄰區間的線段不與圓周相交,接著按照線段中點由遠到近排序,相鄰兩個線段構成梯形,要不全都在內部、外部或者是切一半,切一半也相當好求,拿一個扇形去扣掉一個三角形即可。

算法參考用圖示

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
#include <stdio.h>
#include <math.h>
#include <algorithm>
#include <set>
#include <map>
#include <assert.h>
#include <vector>
#include <string.h>
using namespace std;
#define eps 1e-9
struct Pt {
double x, y;
Pt(double a = 0, double 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);
}
bool operator==(const Pt &a) const {
return fabs(x - a.x) < eps && fabs(y - a.y) < eps;
}
bool operator!=(const Pt &a) const {
return !(a == *this);
}
bool operator<(const Pt &a) const {
if (fabs(x - a.x) > eps)
return x < a.x;
if (fabs(y - a.y) > eps)
return y < a.y;
return false;
}
double length() {
return hypot(x, y);
}
void read() {
scanf("%lf %lf", &x, &y);
}
};
const double pi = acos(-1);
int cmpZero(double v) {
if (fabs(v) > eps) return v > 0 ? 1 : -1;
return 0;
}
double dot(Pt a, Pt b) {
return a.x * b.x + a.y * b.y;
}
double cross(Pt o, Pt a, Pt b) {
return (a.x-o.x)*(b.y-o.y)-(a.y-o.y)*(b.x-o.x);
}
double cross2(Pt a, Pt b) {
return a.x * b.y - a.y * b.x;
}
int between(Pt a, Pt b, Pt c) {
return dot(c - a, b - a) >= -eps && dot(c - b, a - b) >= -eps;
}
int onSeg(Pt a, Pt b, Pt c) {
return between(a, b, c) && fabs(cross(a, b, c)) < eps;
}
struct Seg {
Pt s, e;
int label;
Seg(Pt a = Pt(), Pt b = Pt(), int l=0): s(a), e(b), label(l) {
}
bool operator!=(const Seg &other) const {
return !((s == other.s && e == other.e) || (e == other.s && s == other.e));
}
};
int intersection(Pt as, Pt at, Pt bs, Pt bt) {
if (cmpZero(cross(as, at, bs) * cross(as, at, bt)) < 0 &&
cmpZero(cross(bs, bt, as) * cross(bs, bt, at)) < 0)
return 1;
return 0;
}
Pt getIntersect(Seg a, Seg b) {
Pt u = a.s - b.s;
double t = cross2(b.e - b.s, u)/cross2(a.e - a.s, b.e - b.s);
return a.s + (a.e - a.s) * t;
}
double getAngle(Pt va, Pt vb) { // segment, not vector
return acos(dot(va, vb) / va.length() / vb.length());
}
Pt rotateRadian(Pt a, double radian) {
double x, y;
x = a.x * cos(radian) - a.y * sin(radian);
y = a.x * sin(radian) + a.y * cos(radian);
return Pt(x, y);
}
int inPolygon(vector<Pt> &p, Pt q) {
int i, j, cnt = 0;
int n = p.size();
for(i = 0, j = n-1; i < n; j = i++) {
if(onSeg(p[i], p[j], q))
return 1;
if(p[i].y > q.y != p[j].y > q.y &&
q.x < (p[j].x-p[i].x)*(q.y-p[i].y)/(p[j].y-p[i].y) + p[i].x)
cnt++;
}
return cnt&1;
}
double polygonArea(vector<Pt> &p) {
double area = 0;
int n = p.size();
for(int i = 0; i < n;i++)
area += p[i].x * p[(i+1)%n].y - p[i].y * p[(i+1)%n].x;
return fabs(area) /2;
}
Pt projectLine(Pt as, Pt ae, Pt p) {
double a, b, c, v;
a = as.y - ae.y, b = ae.x - as.x;
c = - (a * as.x + b * as.y);
v = a * p.x + b * p.y + c;
return Pt(p.x - v*a / (a*a+b*b), p.y - v*b/ (a*a+b*b));
}
//
vector<Pt> circleInterectSeg(Pt a, Pt b, double r) {
vector<Pt> ret;
Pt c, vab, p;
double v, lab;
c = projectLine(a, b, Pt(0, 0));
vab = a - b, lab = (a - b).length();
if (cmpZero(c.x * c.x + c.y * c.y - r * r) > 0)
return ret;
v = sqrt(r * r - (c.x * c.x + c.y * c.y));
vab = vab * (v / lab);
p = c + vab;
if (onSeg(a, b, p))
ret.push_back(p);
p = c - vab;
if (onSeg(a, b, p))
ret.push_back(p);
if (ret.size() == 2 && ret[0] == ret[1])
ret.pop_back();
return ret;
}
bool cmp(pair<double, Pt> a, pair<double, Pt> b) {
return a.first < b.first;
}
bool cmp2(pair<double, Seg> a, pair<double, Seg> b) {
return a.first < b.first;
}
double scan(vector<Pt> poly, double r) {
int n = poly.size();
vector<Pt> all;
for (int i = 0, j = n-1; i < n; j = i++) {
all.push_back(poly[i]);
all.push_back(poly[j]);
vector<Pt> inter = circleInterectSeg(poly[i], poly[j], r);
for (int k = 0; k < inter.size(); k++)
all.push_back(inter[k]);
}
sort(all.begin(), all.end());
all.resize(unique(all.begin(), all.end()) - all.begin());
vector< pair<double, Pt> > polar;
for (int i = 0; i < all.size(); i++) {
Pt p = all[i];
polar.push_back(make_pair(atan2(p.y, p.x), p));
}
sort(polar.begin(), polar.end(), cmp);
double ret = 0;
for (int i = 0; i < polar.size(); ) {
vector<Pt> A, B;
int idx1, idx2;
double ltheta, rtheta;
idx1 = i, ltheta = polar[i].first;
while (idx1 < polar.size() && cmpZero(polar[i].first - polar[idx1].first) == 0)
A.push_back(polar[idx1].second), idx1++;
if (idx1 == polar.size()) // end
break;
idx2 = idx1, rtheta = polar[idx1].first;
while (idx2 < polar.size() && cmpZero(polar[idx1].first - polar[idx2].first) == 0)
B.push_back(polar[idx2].second), idx2++;
i = idx1;
if (A.size() == 0 || B.size() == 0)
assert(false);
for (int j = 0, k = n-1; j < n; k = j++) {
if (cmpZero(cross(Pt(0, 0), A[0], poly[j])) * cmpZero(cross(Pt(0, 0), A[0], poly[k])) < 0)
A.push_back(getIntersect(Seg(Pt(0, 0), A[0]), Seg(poly[j], poly[k])));
if (cmpZero(cross(Pt(0, 0), B[0], poly[j])) * cmpZero(cross(Pt(0, 0), B[0], poly[k])) < 0)
B.push_back(getIntersect(Seg(Pt(0, 0), B[0]), Seg(poly[j], poly[k])));
}
A.push_back(Pt(0, 0));
B.push_back(Pt(0, 0));
sort(A.begin(), A.end());
sort(B.begin(), B.end());
A.resize(unique(A.begin(), A.end()) - A.begin());
B.resize(unique(B.begin(), B.end()) - B.begin());
vector< pair<double, Seg> > crossEdge;
for (int p = 0; p < A.size(); p++) {
for (int q = 0; q < B.size(); q++) {
if (A[p] == B[q] || A[p] == Pt(0, 0) || B[q] == Pt(0, 0))
continue;
for (int j = 0, k = n-1; j < n; k = j++) {
if (onSeg(poly[j], poly[k], A[p]) && onSeg(poly[j], poly[k], B[q])) {
Pt mid = (A[p] + B[q]) * 0.5;
crossEdge.push_back(make_pair((mid - Pt(0, 0)).length(), Seg(A[p], B[q])));
}
}
}
}
crossEdge.push_back(make_pair(0.0, Seg(Pt(0, 0), Pt(0, 0))));
sort(crossEdge.begin(), crossEdge.end(), cmp2);
for (int j = 0; j < crossEdge.size() - 1; j++) {
Seg a = crossEdge[j].second;
Seg b = crossEdge[j+1].second;
Pt ma = (a.s + a.e) * 0.5;
Pt mb = (b.s + b.e) * 0.5;
Pt mab = (ma + mb) * 0.5;
if (!inPolygon(poly, mab))
continue;
double area = (fabs(cross(b.s, b.e, a.e)) + fabs(cross(a.s, a.e, b.s))) /2;
int inout[4] = {}, all_in, all_out;
inout[0] = cmpZero((a.s - Pt(0, 0)).length() - r) <= 0;
inout[1] = cmpZero((a.e - Pt(0, 0)).length() - r) <= 0;
inout[2] = cmpZero((b.s - Pt(0, 0)).length() - r) <= 0;
inout[3] = cmpZero((b.e - Pt(0, 0)).length() - r) <= 0;
all_in = inout[0] & inout[1] & inout[2] & inout[3];
all_out = (!inout[0]) & (!inout[1]) & (!inout[2]) & (!inout[3]);
// printf("area %lf\n", area);
// printf("%lf %lf, %lf %lf\n", a.s.x, a.s.y, a.e.x, a.e.y);
// printf("%lf %lf, %lf %lf\n", b.s.x, b.s.y, b.e.x, b.e.y);
if (all_out) {
// printf("no %lf\n", 0);
continue;
}
if (all_in) {
// printf("all %lf\n", area);
ret += area;
continue;
}
if (inout[0] == 1 && inout[1] == 1) {
// printf("part %lf\n", r * r * (rtheta - ltheta)/2 - fabs(cross(Pt(0, 0), a.s, a.e)) /2);
ret += r * r * (rtheta - ltheta)/2 - fabs(cross(Pt(0, 0), a.s, a.e)) /2;
} else {
// printf("no %lf\n", 0);
}
}
// puts("---");
}
return ret;
}
int main() {
int n;
double r, x, y;
while (scanf("%d %lf", &n, &r) == 2) {
vector<Pt> poly;
for (int i = 0; i < n; i++) {
scanf("%lf %lf", &x, &y);
poly.push_back(Pt(x, y));
}
double ret = scan(poly, r);
printf("%.9lf\n", ret);
}
return 0;
}
/*
6 10
-8 2
8 2
8 14
0 14
0 6
-8 14
4 10
10 0
10 10
-10 10
-10 0
6 10
2 2
12 2
6 4
12 4
8 8
-2 8
5 10
-4 6
-2 2
0 4
4 2
8 4
*/

Version 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
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
#include <stdio.h>
#include <math.h>
#include <algorithm>
#include <set>
#include <map>
#include <assert.h>
#include <vector>
#include <string.h>
using namespace std;
#define eps 1e-9
struct Pt {
double x, y;
Pt(double a = 0, double 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);
}
bool operator==(const Pt &a) const {
return fabs(x - a.x) < eps && fabs(y - a.y) < eps;
}
bool operator!=(const Pt &a) const {
return !(a == *this);
}
bool operator<(const Pt &a) const {
if (fabs(x - a.x) > eps)
return x < a.x;
if (fabs(y - a.y) > eps)
return y < a.y;
return false;
}
double length() {
return hypot(x, y);
}
void read() {
scanf("%lf %lf", &x, &y);
}
};
const double pi = acos(-1);
int cmpZero(double v) {
if (fabs(v) > eps) return v > 0 ? 1 : -1;
return 0;
}
double dot(Pt a, Pt b) {
return a.x * b.x + a.y * b.y;
}
double cross(Pt o, Pt a, Pt b) {
return (a.x-o.x)*(b.y-o.y)-(a.y-o.y)*(b.x-o.x);
}
double cross2(Pt a, Pt b) {
return a.x * b.y - a.y * b.x;
}
int between(Pt a, Pt b, Pt c) {
return dot(c - a, b - a) >= -eps && dot(c - b, a - b) >= -eps;
}
int onSeg(Pt a, Pt b, Pt c) {
return between(a, b, c) && fabs(cross(a, b, c)) < eps;
}
struct Seg {
Pt s, e;
int label;
Seg(Pt a = Pt(), Pt b = Pt(), int l=0): s(a), e(b), label(l) {
}
bool operator!=(const Seg &other) const {
return !((s == other.s && e == other.e) || (e == other.s && s == other.e));
}
};
int intersection(Pt as, Pt at, Pt bs, Pt bt) {
if (cmpZero(cross(as, at, bs) * cross(as, at, bt)) < 0 &&
cmpZero(cross(bs, bt, as) * cross(bs, bt, at)) < 0)
return 1;
return 0;
}
Pt getIntersect(Seg a, Seg b) {
Pt u = a.s - b.s;
double t = cross2(b.e - b.s, u)/cross2(a.e - a.s, b.e - b.s);
return a.s + (a.e - a.s) * t;
}
double getAngle(Pt va, Pt vb) { // segment, not vector
return acos(dot(va, vb) / va.length() / vb.length());
}
Pt rotateRadian(Pt a, double radian) {
double x, y;
x = a.x * cos(radian) - a.y * sin(radian);
y = a.x * sin(radian) + a.y * cos(radian);
return Pt(x, y);
}
int inPolygon(vector<Pt> &p, Pt q) {
int i, j, cnt = 0;
int n = p.size();
for(i = 0, j = n-1; i < n; j = i++) {
if(onSeg(p[i], p[j], q))
return 1;
if(p[i].y > q.y != p[j].y > q.y &&
q.x < (p[j].x-p[i].x)*(q.y-p[i].y)/(p[j].y-p[i].y) + p[i].x)
cnt++;
}
return cnt&1;
}
double polygonArea(vector<Pt> &p) {
double area = 0;
int n = p.size();
for(int i = 0; i < n;i++)
area += p[i].x * p[(i+1)%n].y - p[i].y * p[(i+1)%n].x;
return fabs(area) /2;
}
Pt projectLine(Pt as, Pt ae, Pt p) {
double a, b, c, v;
a = as.y - ae.y, b = ae.x - as.x;
c = - (a * as.x + b * as.y);
v = a * p.x + b * p.y + c;
return Pt(p.x - v*a / (a*a+b*b), p.y - v*b/ (a*a+b*b));
}
//
vector<Pt> circleInterectSeg(Pt a, Pt b, double r) { // maybe return same point
vector<Pt> ret;
Pt c, vab, p;
double v, lab;
c = projectLine(a, b, Pt(0, 0));
if (cmpZero(c.x*c.x + c.y*c.y - r*r) > 0)
return ret;
vab = a - b, lab = (a - b).length();
v = sqrt(r * r - (c.x * c.x + c.y * c.y));
vab = vab * (v / lab);
if (onSeg(a, b, c + vab))
ret.push_back(c + vab);
if (onSeg(a, b, c - vab))
ret.push_back(c - vab);
return ret;
}
double circleWithTriangle(double r, Pt a, Pt b) { // get intersect area
Pt o = Pt(0, 0);
double la = (a - Pt(0, 0)).length();
double lb = (b - Pt(0, 0)).length();
if (cmpZero(cross(o, a, b)) == 0)
return 0;
if (cmpZero(la - r) <= 0 && cmpZero(lb - r) <= 0)
return fabs(cross(o, a, b))/2;
if (cmpZero(la - r) <= 0 || cmpZero(lb - r) <= 0) {
if (cmpZero(la - r) > 0)
swap(a, b), swap(la, lb);
// a in circle, b not in circle
vector<Pt> c = circleInterectSeg(a, b, r);
assert(c.size() > 0);
if (c.size() > 1 && c[0] == a)
swap(c[0], c[1]);
double theta = getAngle(c[0], b);
double ret = 0;
ret += fabs(cross(o, a, c[0]))/2;
ret += r * r * theta/2;
return ret;
}
// a not in circle, b not in circle
vector<Pt> c = circleInterectSeg(a, b, r);
if (c.size() == 0) {
double theta = getAngle(a, b);
return r * r * theta/2;
} else {
assert(c.size() == 2);
if (dot(c[0] - a, b - a) > dot(c[1] - a, b - a))
swap(c[0], c[1]);
double theta1 = getAngle(a, c[0]);
double theta2 = getAngle(c[1], b);
double ret = 0;
ret += r * r * (theta1+theta2)/2;
ret += fabs(cross(o, c[0], c[1]))/2;
return ret;
}
return 0;
}
int main() {
int n;
double r, x, y;
while (scanf("%d %lf", &n, &r) == 2) {
vector<Pt> poly;
for (int i = 0; i < n; i++) {
scanf("%lf %lf", &x, &y);
poly.push_back(Pt(x, y));
}
double ret = 0;
for (int i = 0; i < n; i++) {
double area = circleWithTriangle(r, poly[i], poly[(i+1)%n]);
if (cmpZero(cross(Pt(0, 0), poly[i], poly[(i+1)%n])) > 0)
ret += area;
else
ret -= area;
}
printf("%.9lf\n", fabs(ret));
}
return 0;
}
/*
6 10
-8 2
8 2
8 14
0 14
0 6
-8 14
4 10
10 0
10 10
-10 10
-10 0
6 10
2 2
12 2
6 4
12 4
8 8
-2 8
5 10
-4 6
-2 2
0 4
4 2
8 4
*/
Read More +

UVa 1566 - John

Problem

撿石子遊戲,但是相反地拿走最後一個的人輸。

(通常 Nim 遊戲都是無法進行操作的人輸)

Sample Input

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

Sample Output

1
2
John
Brother

Solution

上網搜索一下 Sprague Grundy - Jia Zhihao,簡單地說曾經有個大陸人在選訓隊講這個,所以不太算是定理名稱。

有興趣的人可以上網搜尋,主要細分四種狀態,是否每一堆大小都為 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
// Anti-SG, Sprague Grundy - Jia Zhihao
#include <stdio.h>
int main() {
int testcase, cases = 0;
int n;
scanf("%d", &testcase);
while (testcase--) {
scanf("%d", &n);
int sg = 0, s1 = 1;
int x;
for (int i = 0; i < n; i++) {
scanf("%d", &x);
sg ^= x;
s1 &= x <= 1;
}
if (s1)
printf("%s\n", sg == 0 ? "John" : "Brother");
else
printf("%s\n", sg ? "John" : "Brother");
}
return 0;
}
Read More +

UVa 1558 - Number Game

Problem

給予 2 到 20 的內數字,兩名玩家輪流挑一個數字 x,並且將 x 的倍數遮蔽、x 倍數和已經被遮蔽的數字和也應該被遮蔽。

被遮蔽的數字將無法被使用。無法選擇任何數字的人輸。給定盤面上剩餘可選的數字,請問先手在第一步可以選擇哪幾個數字獲得勝利。

Sample Input

1
2
3
4
5
2
1
2
2
2 3

Sample Output

1
2
3
4
5
Scenario #1:
The winning moves are: 2.
Scenario #2:
There is no winning move.

Solution

由於數字量很小,可以進行 bitmask 壓縮,來得知數字的使用情況。接著套用博弈 dp 的思路,來得知必勝狀態!

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
#include <stdio.h>
#include <vector>
#include <string.h>
#include <algorithm>
using namespace std;
int dp[1<<20];
int pick(int state, int x) {
for (int i = x; i <= 20; i += x)
if ((state>>(i-2))&1)
state ^= 1<<(i-2);
for (int i = x; i <= 20; i++) {
if ((state>>(i-2))&1) {
if (i - x >= 2) {
if (((state>>(i - x -2))&1) == 0)
state ^= 1<<(i-2);
}
}
}
return state;
}
int dfs(int state) {
if (state == 0)
return 0;
if (dp[state] != -1)
return dp[state];
int &ret = dp[state];
ret = 0;
for (int i = 2; i <= 20; i++) {
if ((state>>(i-2))&1) {
int v = dfs(pick(state, i));
ret |= !v;
if (ret) break;
}
}
return ret;
}
int main() {
memset(dp, -1, sizeof(dp));
int testcase, cases = 0;
int n;
scanf("%d", &testcase);
while (testcase--) {
scanf("%d", &n);
int mask = 0, x;
for (int i = 0; i < n; i++) {
scanf("%d", &x);
mask |= 1<<(x-2);
}
printf("Scenario #%d:\n", ++cases);
vector<int> ret;
for (int i = 2; i <= 20; i++) {
if ((mask>>(i-2))&1) {
int v = dfs(pick(mask, i));
if (v == 0)
ret.push_back(i);
}
}
if (ret.size() == 0)
puts("There is no winning move.");
else {
printf("The winning moves are:");
for (int i = 0; i < ret.size(); i++)
printf(" %d", ret[i]);
puts(".");
}
puts("");
}
return 0;
}
Read More +

UVa 1557 - Calendar Game

Problem

給一個起始日期,兩名玩家輪流操作,操作方式有兩種,將日期往後延一天,將月份往後延一個月,抵達 November 4, 2001 的人獲勝,如果發生移動到不合法的日期則視為無效。

必須考慮閏年變化。

Sample Input

1
2
3
4
3
2001 11 3
2001 11 2
2001 10 3

Sample Output

1
2
3
YES
NO
NO

Solution

由於年份差最多 100,可以直接根據年月日做狀態搜索。要處理移動時,發生跨年、跨月,這方面會稍微麻煩一點。閏年要額外處理對 2/29 的日期判斷,並且移動時不可超出指定的日期之後。

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
#include <stdio.h>
#include <string.h>
#include <algorithm>
using namespace std;
const int mday[13] = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
int dp[128][16][32], used[128][16][32], dcases = 0;
int validDate(int yyyy, int mm, int dd) {
if (mm < 1 || mm > 12)
return 0;
if ((yyyy%4 == 0 && yyyy%100 != 0) || (yyyy%400) == 0) {
if (mm == 2) {
if (dd < 1 || dd > 29)
return 0;
} else {
if (dd < 1 || dd > mday[mm])
return 0;
}
} else {
if (dd < 1 || dd > mday[mm])
return 0;
}
return 1;
}
int complete(int yyyy, int mm, int dd) {
return yyyy == 2001 && mm == 11 && dd == 4;
}
int fail(int yyyy, int mm, int dd) {
if (!validDate(yyyy, mm, dd))
return 1;
if (yyyy > 2001) return 1;
if (yyyy < 2001) return 0;
if (mm > 11) return 1;
if (mm < 11) return 0;
if (dd > 4) return 1;
if (dd < 4) return 0;
return 0;
}
void nextMonth(int &yyyy, int &mm, int &dd) {
mm++;
if (mm == 13) yyyy++, mm = 1;
}
void nextDay(int &yyyy, int &mm, int &dd) {
int mday[13] = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
if ((yyyy%4 == 0 && yyyy%100 != 0) || (yyyy%400) == 0)
mday[2] = 29;
dd++;
if (dd > mday[mm])
mm++, dd = 1;
if (mm == 13) yyyy++, mm = 1;
}
int dfs(int yyyy, int mm, int dd) {
if (fail(yyyy, mm, dd))
return 0;
if (complete(yyyy, mm, dd))
return 1;
if (used[yyyy-1900][mm][dd] == dcases)
return dp[yyyy-1900][mm][dd];
int &ret = dp[yyyy-1900][mm][dd];
int y, m ,d;
ret = 0, used[yyyy-1900][mm][dd] = dcases;
y = yyyy, m = mm, d = dd;
nextMonth(y, m, d);
if (complete(y, m, d))
ret = 1;
if (!fail(y, m, d))
ret |= !dfs(y, m, d);
y = yyyy, m = mm, d = dd;
nextDay(y, m, d);
if (complete(y, m, d))
ret = 1;
if (!fail(y, m, d))
ret |= !dfs(y, m, d);
return ret;
}
int main() {
int testcase;
int yyyy, mm, dd;
scanf("%d", &testcase);
while (testcase--) {
scanf("%d %d %d", &yyyy, &mm, &dd);
dcases ++;
printf("%s\n", dfs(yyyy, mm, dd) ? "YES" : "NO");
}
return 0;
}
/*
3
2001 11 3
2001 11 2
2001 10 3
*/
Read More +

UVa 1534 - Taekwondo

Problem

題目模型與 Zerojudge a192 接線問題 一樣。

題目背景在跆拳道選手,有兩團人馬,希望兩兩配對,體重差的絕對值總和越小越好。

Sample Input

1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
2 3
44.9
50.0
77.2
86.4
59.8
4 2
44.9
50.0
77.2
86.4
59.8
58.9

Sample Output

1
2
42.1
23.8

Solution

按照以前的思路來看,維護的是前一個最後匹配到的人是誰,這樣的效率保證是 $O(N^2)$,這樣的效能已經相當快,中間運用到維護最小值的技巧,由於前 i-1 個人匹配到前 j 個人的最小值,那麼 i 匹配到 k 時,需要的是 min(dp[i-1][0 <= j < k]),邊掃描邊維護。

看到網路上有一個不錯的定義,採用失匹配幾個人,較多人數的那一方,將會有幾個人無法匹配,也因此紀錄第 i 個人時,另一團人有 j 個人沒匹配到,那麼當前第 i 個人,必然要與編號 i + j 的人匹配。

忘了提及,一定要先排序,轉換到接線問題時,交錯匹配的距離會比較長,這部分屬於貪心。

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
#include <stdio.h>
#include <vector>
#include <assert.h>
#include <algorithm>
using namespace std;
int dp[512][512]; // dp[i-th match in A][#miss match in B]
void solve(vector<int> A, vector<int> B) {
assert(A.size() <= B.size());
int n1 = A.size(), n2 = B.size();
int diff = n2 - n1;
sort(A.begin(), A.end());
sort(B.begin(), B.end());
for (int i = 0; i < n1; i++) {
int mn = 0x3f3f3f3f;
for (int j = 0; j <= diff; j++) {
if (i == 0)
mn = 0;
else
mn = min(mn, dp[i-1][j]);
dp[i][j] = mn + abs(A[i] - B[i + j]);
}
}
int ret = 0x3f3f3f3f;
for (int i = 0; i <= diff; i++)
ret = min(ret, dp[n1-1][i]);
printf("%d.%d\n", ret/10, ret%10);
}
int main() {
int testcase;
int n1, n2, x, y;
scanf("%d", &testcase);
while (testcase--) {
scanf("%d %d", &n1, &n2);
vector<int> A, B;
for (int i = 0; i < n1; i++) {
scanf("%d.%d", &x, &y);
A.push_back(x * 10 + y);
}
for (int i = 0; i < n2; i++) {
scanf("%d.%d", &x, &y);
B.push_back(x * 10 + y);
}
if (n1 < n2)
solve(A, B);
else
solve(B, A);
}
return 0;
}
Read More +

UVa 996 - Find the Sequence

Problem

這一題是 UVa 997 - Show the Sequence 的反運算,給定序列找到操作的產生方法。

Sample Input

1
2
3 10 30 30 -30 90 -450 3150
2 2 6 36 360 5400 113400

Sample Output

1
2
[2*[5+[-2]]]
[0]

Solution

由於每一個數字不算大,而且從操作方法中得知,數字大小的增長速度非常快,對於累加的部分可以 O(1) 窮舉,但是對於乘積部分需要窮舉因數來得知。

由於題目不是 special judge 也擔心就算找到答案是不是唯一解,按著 Dfs 搜索的順序就 AC 了。之前寫的版本還真是有點微妙。

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
#include <stdio.h>
#include <set>
#include <vector>
#include <iostream>
#include <sstream>
#include <algorithm>
using namespace std;
string num2str(int x) {
string s;
stringstream sin(s);
sin << x;
return sin.str();
}
int dfs(vector<int> A, int M, string &solution) {
// printf("[");
// for (int i = 0; i < A.size(); i++)
// printf("%d ", A[i]);
// puts("]");
int same = 1;
for (int i = 1; i < A.size(); i++)
same &= A[i] == A[0];
if (same == 1) {
solution = "[" + num2str(A[0]) + "]";
return 1;
}
if (M <= 0) return 0;
int g = A[0];
for (int i = 0; i < A.size(); i++) {
if (A[i] == 0) {
g = 0;
break;
}
g = __gcd(g, A[i]);
}
for (int i = 1; i < A.size(); i++) {
if (A[i-1] != 0 && A[i]%A[i-1] != 0)
g = 0;
if (A[i-1] == 0 && A[i] != 0)
g = 0;
}
if (g < 0) g = -g;
if (g > 0) {
for (int m = 1; m <= g; m++) {
if (g%m == 0) {
vector<int> nA;
nA.push_back(A[0] / m);
for (int j = 1; j < A.size(); j++) {
if (A[j-1] == 0)
nA.push_back(0);
else
nA.push_back(A[j] / A[j-1]);
}
if (dfs(nA, M-1, solution)) {
solution = "[" + num2str(m) + "*" + solution + "]";
return 1;
}
nA.clear();
nA.push_back(A[0] / (-m));
for (int j = 1; j < A.size(); j++) {
if (A[j-1] == 0)
nA.push_back(0);
else
nA.push_back(A[j] / A[j-1]);
}
if (dfs(nA, M-1, solution)) {
solution = "[" + num2str(-m) + "*" + solution + "]";
return 1;
}
}
}
}
vector<int> nA;
for (int i = 1; i < A.size(); i++)
nA.push_back(A[i] - A[i-1]);
if (dfs(nA, M-1, solution)) {
solution = "[" + num2str(A[0]) + "+" + solution + "]";
return 1;
}
return 0;
}
int main() {
int M, x;
string line;
while (getline(cin, line)) {
stringstream sin(line);
sin >> M;
vector<int> A;
while (sin >> x)
A.push_back(x);
string solution;
int f = dfs(A, M, solution);
if (f == 0)
puts("[0]");
else
puts(solution.c_str());
}
return 0;
}
/*
3 10 30 30 -30 90 -450 3150
2 2 6 36 360 5400 113400
*/
Read More +