b405. 記憶中的序列

Problem

給一個序列,支持插入元素、刪除元素、反轉區間、區間極值、區間總和、區間修改以及最重要的 版本回溯

題目輸入會拿上一個答案進行 XOR 的加密,

Sample Input

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
10
9 81 28 6 40 68 74 50 82 62
15
1 1 83
2 11
3 7 8
4 4 10 28
5 2 6
85 82 89
14 13 13
56 61
57 57 40
58 61
59 61 63
60 57 59 4
61 58 49
137 139 136
37 38 42

decrypt input

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
10
9 81 28 6 40 68 74 50 82 62
15
1 1 83
2 11
3 7 8
4 4 10 28
5 2 6
6 1 10
7 4 4
0 5
1 1 16
2 5
3 5 7
4 1 3 60
5 2 9
6 4 7
7 4 8

Sample Output

1
2
3
4
5
6
83
9
56
143
34
381

Solution

持久化 treap,區間反轉,區間最大值,區間最小值,區間總和,插入元素,刪除元素,操作回溯。

這坑爹的題目描述擺明就是拖人下水,只好含淚地寫下去。持久化類型的題目應該是告一段落,就差一個 LCP + HASH 的維護了吧。麻煩的地方在於持久化的懶操作標記傳遞時都要新增加一個節點,這項舉動導致記憶體量非常多,1 秒處理的資訊吃掉 512 MB。二分記憶體上限,終於找到內存池的最多元素個數,中間不小心把多項資訊的 int 型態宣告成 long long,導致內存池一直不夠大。

如果不考慮回溯操作,速度上沒有比暴力算法來得快上很多,treap 基於隨機數的調整,而且每一次修改元素都要增加很多記憶體來補,導致速度上無法領先太多。但如果用暴力法,會造成版本回溯問題,空間會到 $O(
n^2)$,不允許的狀態,持久化造就的平均空間使用$O(Q log N \times sizeof(Node))$,看起來仍然是很巨大的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
#include <bits/stdc++.h>
using namespace std;
#define MAXN 7100000
#define MAXQ 60005
class PersistentTreap {
public:
struct Node;
static Node *null;
struct Node {
Node *lson, *rson;
int key, size;
long long val, mnval, mxval, sumval;
long long adel; // delivery
int rdel;
Node(long long c = 0, int s = 1): size(s) {
lson = rson = null;
key = rand();
val = mnval = mxval = sumval = c;
adel = rdel = 0;
}
void update() {
size = 1;
size += lson->size + rson->size;
}
void pushUp() {
mnval = mxval = sumval = val;
if (lson != null) {
mnval = min(mnval, lson->mnval + lson->adel);
mxval = max(mxval, lson->mxval + lson->adel);
sumval += lson->sumval + lson->adel * lson->size;
}
if (rson != null) {
mnval = min(mnval, rson->mnval + rson->adel);
mxval = max(mxval, rson->mxval + rson->adel);
sumval += rson->sumval + rson->adel * rson->size;
}
}
void pushDown(PersistentTreap &treap) {
if (adel) {
val += adel, mnval += adel, mxval += adel;
if (lson != null) {
lson = treap.cloneNode(lson);
lson->adel += adel;
}
if (rson != null) {
rson = treap.cloneNode(rson);
rson->adel += adel;
}
adel = 0;
}
if (rdel&1) {
if (lson == null)
lson = rson, rson = null;
else if (rson == null)
rson = lson, lson = null;
else
swap(lson, rson);
if (lson != null) {
lson = treap.cloneNode(lson);
lson->rdel += rdel;
}
if (rson != null) {
rson = treap.cloneNode(rson);
rson->rdel += rdel;
}
rdel = 0;
}
pushUp();
}
} nodes[MAXN], *root[MAXQ];
int bufIdx, verIdx;
Node* merge(Node* a, Node* b) {
if (a == null) return cloneNode(b);
if (b == null) return cloneNode(a);
if (a != null) a->pushDown(*this);
if (b != null) b->pushDown(*this);
Node *ret;
if (a->key < b->key) {
ret = cloneNode(a);
ret->rson = merge(a->rson, b);
} else {
ret = cloneNode(b);
ret->lson = merge(a, b->lson);
}
ret->update();
ret->pushUp();
return ret;
}
void split(Node* a, Node* &l, Node* &r, int n) {
if (n == 0) {
l = null, r = cloneNode(a);
return ;
}
if (a->size <= n) {
l = cloneNode(a), r = null;
return ;
}
if (a != null)
a->pushDown(*this);
if (a->lson->size >= n) {
r = cloneNode(a);
split(a->lson, l, r->lson, n);
r->update();
r->pushUp();
} else {
l = cloneNode(a);
split(a->rson, l->rson, r, n - (a->lson->size) - 1);
l->update();
l->pushUp();
}
}
void insert(Node* &a, Node *ver, int pos, long long s[], int sn) {
Node *p, *q, *r;
int n = sn;
split(ver, p, q, pos);
build(r, 0, n - 1, s);
p = merge(p, r);
a = merge(p, q);
}
void erase(Node* &a, Node *ver, int pos, int n) {
Node *p, *q, *r;
split(ver, p, q, pos - 1);
split(q, q, r, n);
a = merge(p, r);
}
void reverse(Node* &a, Node *ver, int left, int right) {
Node *p, *q, *r;
split(ver, p, q, left - 1);
split(q, q, r, right - left + 1);
q->rdel++;
a = merge(p, q);
a = merge(a, r);
}
void add(Node* &a, Node *ver, int left, int right, long long val) {
Node *p, *q, *r;
split(ver, p, q, left - 1);
split(q, q, r, right - left + 1);
q->adel += val;
a = merge(p, q);
a = merge(a, r);
}
long long q_sum, q_max, q_min;
void q_dfs(Node *u, int left, int right) {
left = max(left, 1);
right = min(right, u->size);
if (left > right)
return;
if (u != null)
u->pushDown(*this);
int lsz = u->lson != null ? u->lson->size : 0;
int rsz = u->rson != null ? u->rson->size : 0;
if (left == 1 && right == lsz + rsz + 1) {
q_sum += u->sumval;
q_max = max(q_max, u->mxval);
q_min = min(q_min, u->mnval);
return ;
}
if (left <= lsz+1 && lsz+1 <= right) {
q_sum += u->val;
q_max = max(q_max, u->val);
q_min = min(q_min, u->val);
}
if (u->lson != null && left <= lsz)
q_dfs(u->lson, left, right);
if (u->rson != null && right > lsz+1)
q_dfs(u->rson, left - lsz - 1, right - lsz - 1);
}
void q_init() {
q_max = LONG_LONG_MIN;
q_min = LONG_LONG_MAX;
q_sum = 0;
}
long long findMax(Node *ver, int left, int right) {
q_init();
q_dfs(ver, left, right);
return q_max;
}
long long findMin(Node *ver, int left, int right) {
q_init();
q_dfs(ver, left, right);
return q_min;
}
long long findSum(Node *ver, int left, int right) {
q_init();
q_dfs(ver, left, right);
return q_sum;
}
void print(Node *ver) {
if (ver == null) return;
ver->pushDown(*this);
print(ver->lson);
printf("[%3lld]", ver->val);
print(ver->rson);
}
void init() {
bufIdx = verIdx = 0;
root[verIdx] = null;
}
private:
Node* cloneNode(Node* u) {
Node *ret;
if (u == null) {
return u;
} else {
if (bufIdx >= MAXN)
exit(0);
assert(bufIdx < MAXN);
ret = &nodes[bufIdx++];
*ret = *u;
return ret;
}
}
void build(Node* &a, int l, int r, long long s[]) {
if (l > r) return ;
int m = (l + r) /2;
Node u = Node(s[m]), *p = &u, *q;
a = cloneNode(p), p = null, q = null;
build(p, l, m-1, s);
build(q, m+1, r, s);
p = merge(p, a);
a = merge(p, q);
a->update();
a->pushUp();
}
} tree;
PersistentTreap::Node t(0, 0);
PersistentTreap::Node *PersistentTreap::null = &t;
int main() {
int N, Q;
long long A[65536];
long long cmd, x, y, v;
while (scanf("%d", &N) == 1) {
for (int i = 1; i <= N; i++)
scanf("%lld", &A[i]);
tree.init();
tree.insert(tree.root[0], tree.null, 0, A+1, N);
scanf("%d", &Q);
int verIdx = 0;
long long encrypt = 0, ret = 0;
for (int i = 1; i <= Q; i++) {
scanf("%lld", &cmd);
cmd ^= encrypt;
if (cmd == 1) { // insert
scanf("%lld %lld", &x, &v);
x ^= encrypt, v ^= encrypt;
long long B[] = {v};
tree.insert(tree.root[i], tree.root[verIdx], (int) x, B, 1);
verIdx = i;
} else if (cmd == 2) { // erase
scanf("%lld", &x);
x ^= encrypt;
tree.erase(tree.root[i], tree.root[verIdx], (int) x, 1);
verIdx = i;
} else if (cmd == 3) { // reverse
scanf("%lld %lld", &x, &y);
x ^= encrypt, y ^= encrypt;
tree.reverse(tree.root[i], tree.root[verIdx], (int) x, (int) y);
verIdx = i;
} else if (cmd == 4) { // [x, y] += v
scanf("%lld %lld %lld", &x, &y, &v);
x ^= encrypt, y ^= encrypt, v ^= encrypt;
tree.add(tree.root[i], tree.root[verIdx], (int) x, (int) y, v);
verIdx = i;
} else if (cmd == 5) { // max(A[x:y])
scanf("%lld %lld", &x, &y);
x ^= encrypt, y ^= encrypt;
ret = tree.findMax(tree.root[verIdx], (int) x, (int) y);
printf("%lld\n", ret);
encrypt = ret;
tree.root[i] = tree.root[verIdx];
verIdx = i;
} else if (cmd == 6) { // min(A[x:y])
scanf("%lld %lld", &x, &y);
x ^= encrypt, y ^= encrypt;
ret = tree.findMin(tree.root[verIdx], (int) x, (int) y);
printf("%lld\n", ret);
encrypt = ret;
tree.root[i] = tree.root[verIdx];
verIdx = i;
} else if (cmd == 7) { // sum(A[x:y])
scanf("%lld %lld", &x, &y);
x ^= encrypt, y ^= encrypt;
ret = tree.findSum(tree.root[verIdx], (int) x, (int) y);
printf("%lld\n", ret);
encrypt = ret;
tree.root[i] = tree.root[verIdx];
verIdx = i;
} else if (cmd == 0) { // back Day x-th
scanf("%lld", &x);
x ^= encrypt;
tree.root[i] = tree.root[x];
verIdx = i;
} else {
puts("WTF");
return 0;
}
}
}
return 0;
}

Test Code

暴力法驗證用,不支持加密的輸入運行,用一個 vector 完成。

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 <bits/stdc++.h>
using namespace std;
#define MAXN (1<<19)
#define MAXQ 65536
int main() {
int N, Q;
long long A[65536];
long long cmd, x, y, v;
while (scanf("%d", &N) == 1) {
for (int i = 1; i <= N; i++)
scanf("%lld", &A[i]);
vector< vector<long long> > L;
L.push_back(vector<long long>(A+1, A+1+N));
scanf("%d", &Q);
int verIdx = 0;
long long encrypt = 0, ret = 0;
for (int i = 1; i <= Q; i++) {
scanf("%lld", &cmd);
vector<long long> TA = L[verIdx];
if (cmd == 1) { // insert
scanf("%lld %lld", &x, &v);
TA.insert(TA.begin()+x, v);
} else if (cmd == 2) { // erase
scanf("%lld", &x);
TA.erase(TA.begin()+x-1);
} else if (cmd == 3) { // reverse
scanf("%lld %lld", &x, &y);
reverse(TA.begin()+x-1, TA.begin()+y);
} else if (cmd == 4) { // [x, y] += v
scanf("%lld %lld %lld", &x, &y, &v);
for (int i = (int) x-1; i < y; i++)
TA[i] += v;
} else if (cmd == 5) { // max(A[x:y])
scanf("%lld %lld", &x, &y);
long long ret = LONG_LONG_MIN;
for (int i = (int) x-1; i < y; i++)
ret = max(ret, TA[i]);
printf("%lld\n", ret);
} else if (cmd == 6) { // min(A[x:y])
scanf("%lld %lld", &x, &y);
long long ret = LONG_LONG_MAX;
for (int i = (int) x-1; i < y; i++)
ret = min(ret, TA[i]);
printf("%lld\n", ret);
} else if (cmd == 7) { // sum(A[x:y])
scanf("%lld %lld", &x, &y);
long long ret = 0;
for (int i = (int) x-1; i < y; i++)
ret += TA[i];
printf("%lld\n", ret);
} else if (cmd == 0) { // back Day x-th
scanf("%lld", &x);
TA = L[x];
} else {
puts("WTF");
return 0;
}
L.push_back(TA), verIdx = i;
printf("version %d\n", i);
for (auto &x: TA)
printf("[%3lld]", x);
puts("");
}
}
return 0;
}

Test Generator

小測資檢驗用

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
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <algorithm>
#include <set>
using namespace std;
int main() {
srand (time(NULL));
int testcase, n, m, x, y;
int A[100];
testcase = 1;
freopen("in.txt", "w+t", stdout);
// printf("%d\n", testcase);
while (testcase--) {
n = 32;
printf("%d\n", n);
for (int i = 0; i < n; i++) {
printf("%d%c", rand(), i == n-1 ? '\n' : ' ');
}
m = 1000;
printf("%d\n", m);
vector<int> L;
L.push_back(n);
for (int i = 1; i <= m; i++) {
while (true) {
int cmd = rand()%8;
if (n == 1 && cmd == 2)
continue;
if (cmd == 0) {
int x = rand()%i;
printf("%d %d\n", cmd, x);
n = L[x];
}
if (cmd == 1)
printf("%d %d %d\n", cmd, rand()%(n+1), rand()), n++;
if (cmd == 2)
printf("%d %d\n", cmd, rand()%n+1), n--;
if (cmd == 3) {
x = rand()%n+1, y = rand()%n+1;
if (x > y) swap(x, y);
printf("%d %d %d\n", cmd, x, y);
}
if (cmd == 4) {
x = rand()%n+1, y = rand()%n+1;
if (x > y) swap(x, y);
printf("%d %d %d %d\n", cmd, x, y, rand());
}
if (cmd == 5 || cmd == 6 || cmd == 7) {
x = rand()%n+1, y = rand()%n+1;
if (x > y) swap(x, y);
printf("%d %d %d\n", cmd, x, y);
}
break;
}
L.push_back(n);
}
}
return 0;
}
Read More +

POJ 1151 - Atlantis

Problem

給一堆平行兩軸的矩形,請問面積的聯集為何。

Sample Input

1
2
3
4
2
10 10 20 20
15 15 25 25.5
0

Sample Output

1
2
Test case #1
Total explored area: 180.00

Solution

利用掃描線算法,從 x 軸小掃描到大,維度 y 軸上的覆蓋情況。

若單純用離散方法在 2D 網格上填充,算法複雜度是 $O(n^2)$,最慘情況還會再增加到 $O(n^4)$,平均上來說離散方法是可行,但是記憶體是 $O(n^2)$

由於 ITSA 的那題 n 會高達 30000,於是拿這一題來練手,掃描線算法複雜度 $O(n \log n)$,利用線段樹操作時,維護某個區間的完全覆蓋數,當完全覆蓋數等於 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
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
#include <stdio.h>
#include <string.h>
#include <map>
#include <vector>
#include <math.h>
#include <algorithm>
using namespace std;
const int MAXN = 262144;
class SegmentTree {
public:
struct Node {
int cover; // #cover
double L, R; // value in real line, cover [L, R]
double clen; // cover length
void init(int a = 0, double b = 0, double c = 0, double d = 0) {
cover = a, L = b, R = c, clen = d;
}
} nodes[MAXN];
double 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
double 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 {
double x, yl, yr;
int val;
Event(double a = 0, double b = 0, double c = 0, int d = 0):
x(a), yl(b), yr(c), val(d) {}
bool operator<(const Event &a) const {
return x < a.x;
}
};
double Lx[32767], Ly[32767], Rx[32767], Ry[32767], K[32767];
int N;
double areaCoverAll() {
vector<Event> events;
vector<double> VY;
map<double, int> RY;
for (int i = 0; i < N; i++) {
double x1, x2, y1, y2;
x1 = Lx[i], x2 = Rx[i];
y1 = Ly[i], y2 = Ry[i];
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(events.begin(), events.end());
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;
}
tree.build(1, 0, VY.size()-2);
double 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;
while (scanf("%d", &N) == 1 && N) {
for (int i = 0; i < N; i++)
scanf("%lf %lf %lf %lf", &Lx[i], &Ly[i], &Rx[i], &Ry[i]);
printf("Test case #%d\n", ++cases);
printf("Total explored area: %.2f\n", areaCoverAll());
puts("");
}
return 0;
}
/*
2
10 10 20 20
15 15 25 25.5
*/
Read More +

[ACM 題目] 障礙物轉換

Problem

在計算幾何領域中,機器人規劃路線是一個實用的議題, 由於機器人是一個有體積的物體,要避開其他的障礙物,障礙物碰撞計算會變得相當複雜。由於某 M 沒有學好這一部分,知道一種方法可以將地圖轉換,把機器人轉換成一個點,計算得到新的障礙物,如此一來就不用處理障礙物碰撞。

從上圖中,一個箭頭型機器人要從左下角移動到右上角,假設不考慮物體可以旋轉,請問是否能移動過去。若把機器人當作一點看待,則必須將物體邊緣增加,變成中間那張圖 configuration space,只剩下一個點和數個多邊形障礙物。接著可以轉換成 visibility graph,把剩下的白色空間進行梯形剖分或者三角剖分,將區域表示成一個點,相鄰區域拉一條邊,就成 dual graph,就能套用一般的最短路徑算法,來協助機器人行走。

處理這問題對於某 M 過於困難,於是將問題更簡化些,只需要把中間那一張圖其中一個障礙物變化求出即可。

現在給定一個凸多邊形障礙物以及移動凸多邊形,假設定位點在深黑色點方形部分,藉由貼著障礙物可以構出新的障礙物,請問新的障礙物為何?

Input

第一行會有一個整數 $T$,表示有多少組測資。

每一組測資會包含兩個凸多邊形。第一行有一個整數 $N$ 表示移動凸多邊形的頂點數量,接下來會有 $N$ 行,每一行會有兩個整數 $x, y$ 表示頂點座標。在 $N+1$ 行之後,會有一個整數 $M$ 表示障礙物凸多邊形的頂點數量,接下來會有 $M$ 行,每一行會有兩個整數 $x, y$ 表示頂點座標。

  • $2 < N, M < 512$
  • $-32767 < x, y < 32767$
  • 假設定位點 (深黑色點方形部分) 都設在原點 (0, 0)。
  • 輸入順序會按照順時針或逆時針給定。

Sample Input

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
2
5
-1 -1
-1 1
0 3
1 1
1 -1
4
1 1
8 2
12 -1
6 -4
3
-1 -4
-1 1
1 1
5
1 0
6 5
10 5
10 -3
1 -3

Sample Output

1
2
Case #1: 89.0
Case #2: 115.5

Hint

Solution

可以用 $O(nm \log nm)$ 來完成此題,方法很簡單,先將機器人的圖形按照原點旋轉 180 度,接著將障礙物每一個頂點座標加上機器人的座標,總共會得到 $nm$ 的頂點,套用凸包算法求一次即可。

這一題應用 Minkowski Sum 來完成,對於兩個凸多邊形的 Minkowski Sum,可以在 $O(n+m)$ 時間內完成,將兩個凸包採用逆時針儲存,挑選最左,等價時抓下方 y 座標的點,可以知道新凸包的頂點一定是由兩個凸包的頂點$A_i, B_j$ 疊加而成,一開始抓到的左下方頂點疊加可以得到第一個頂點,接著要按照凸包順序求出,則比較$(A_i, A_{i+1})$$(B_j, B_{j+1})$ 哪一個偏的角度小,就挑選哪一個往前推動 i = i+1 或者是 j = j+1。最後就能在 $O(n+m)$ 時間內完成。

假設不是凸多邊形,兩個簡單多邊形最慘會到 $O(n^2 m^2)$

1
Read More +

[ACM 題目] 優勢商品

Problem

一份產品有很多屬性,把每一個屬性當作一個維度,當成 $D$ 個維度的向量,當維度越多時絕對支配 (各方面都是最好的) 的機率就越低。若把 $D = 2$,就是在二維空間中的支配點 (Zerojudge d555 平面上的極大點)。由於絕對優勢的產品隨著維度增加而變少,定義出一種相對優勢產品,給定一個 $K$,若 $p_i$ 支配 (k-dominate) $p_j$,必須滿足下列三條:

  • $\exists S' \subseteq S, |S'| = K$
  • $\forall s_r \in S', p_i.s_r \geq p_j.s_r$
  • $\exists s_t \in S', p_i.s_t > p_j.s_t$

用中文解釋這幾條數學公式,假設有 6 個維度的產品,目標 $K = 5$,則表示要在 6 的維度中任意挑選 5 個維度比較,若存在一種挑法可以支配,則可以說 $p_i$ k-dominate $p_j$

例如現在有 8 台筆記型電腦,共有 6 種屬性可以比較,數值越大越好。

Notebook CPU RAM HDD HDD S. Q. VRAM
$N_1$ 3 3 5 6 6 8
$N_2$ 9 4 9 7 7 9
$N_3$ 8 4 7 9 2 7
$N_4$ 5 6 8 9 5 9
$N_5$ 9 7 9 6 2 4
$N_6$ 6 6 6 5 3 5
$N_7$ 5 7 3 8 4 6
$N_8$ 4 4 8 6 6 4

若要判斷 $N_2$ 是否支配 $N_3$,從中挑取 (CPU, RAM, HDD, HDD S., Q.) 這 5 個屬性比較,得到 $N_2$ 勝過於 $N_3$。最後可以發現到 $N_2$ 這產品可以支配 $N_1, N_3, N_5, N_6, N_8$

現在問題來了,要找到不被任何產品支配的產品 (k-dominant skyline)。如上表 8 項產品的情況,只會有 $N_2, N_4$

Input

第一行會有一個整數 $T$ 表示有多少組測資。

每一組測資第一行會有三個整數 $N, D, K$,分別表示產品數量、產品屬性種類、以及支配比較的屬性種類。接下來會有 $N$ 行數據,每一行上會有 $D$ 個整數 $s_j$ 表示一個產品的屬性,產品按照輸入順序編號。

  • $N < 1024$
  • $0 < D, K < 8$
  • $0 < s_j < 1000$

Output

對於每組測資輸出一行,輸出該組測資所有的 k-dominant skyline 的產品編號,由小到大輸出。若不存在則輸出 NONE

Sample Input

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
4
2 3 1
20 20 20
20 20 20
2 5 1
20 5 20 20 20
5 20 20 20 20
4 6 5
9 4 9 7 7 9
9 7 9 6 5 4
9 6 9 8 3 3
1 2 3 9 2 2
8 6 5
3 3 5 6 6 8
9 4 9 7 7 9
8 4 7 9 2 7
5 6 8 9 5 9
9 7 9 6 2 4
6 6 6 5 3 5
5 7 3 8 4 6
4 4 8 6 6 4

Sample Output

1
2
3
4
Case #1: 1 2
Case #2: NONE
Case #3: 1
Case #4: 2 4

Solution

這是一篇論文題目 k-dominant skyline set,網路上提出不少算法,看起來都是有容錯率。

這一題只是介紹題,沒有強求高效算法計算,直接 $O(N^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
#include <stdio.h>
#include <stdlib.h>
#include <set>
#include <queue>
#include <string.h>
#include <algorithm>
using namespace std;
const int MAXN = 1024;
int N, D, K;
vector< vector<int> > C;
struct Product {
int v[8], id;
bool operator<(const Product &a) const {
return sum() > a.sum();
}
int sum() const {
int s = 0;
for (int i = 0; i < D; i++)
s += v[i];
return s;
}
int domain(Product &u) {
for (auto &x : C) {
int h1 = 1, h2 = 0;
for (auto &e : x) {
h1 &= v[e] >= u.v[e];
h2 |= v[e] > u.v[e];
}
if (h1 && h2) return 1;
}
return 0;
}
} item[MAXN];
int used[MAXN];
int main() {
int testcase, cases = 0;
scanf("%d", &testcase);
while (testcase--) {
scanf("%d %d %d", &N, &D, &K);
for (int i = 0; i < N; i++) {
for (int j = 0; j < D; j++)
scanf("%d", &item[i].v[j]);
item[i].id = i;
}
sort(item, item+N);
C.clear();
for (int i = 0; i < (1<<D); i++) {
if (__builtin_popcount(i) == K) {
vector<int> A;
for (int j = 0; j < D; j++) {
if ((i>>j)&1)
A.push_back(j);
}
C.push_back(A);
}
}
vector<Product> filter;
vector<int> ret;
for (int i = 0; i < N; i++)
used[i] = 0;
for (int i = 0; i < N; i++) {
if (used[i] == 0) {
filter.push_back(item[i]);
for (int j = i+1; j < N; j++) {
if (used[j] == 0 && item[i].domain(item[j]))
used[j] = 1;
}
}
}
for (int i = 0; i < filter.size(); i++) {
int ok = 1;
for (int j = 0; j < N && ok; j++) {
if (item[j].domain(filter[i]))
ok = 0;
}
if (ok == 1)
ret.push_back(filter[i].id);
}
sort(ret.begin(), ret.end());
printf("Case #%d:", ++cases);
for (int i = 0; i < ret.size(); i++)
printf(" %d", ret[i]+1);
puts(ret.size() ? "" : " NONE");
}
return 0;
}
Read More +

2015 Google Code Jam Round 2

超過凌晨的競賽,隔天起床有一種說不出的疲累感,坑了一個早上,仍然沒想到最後幾題。看來已經玩不下去!圍觀到這裡。

A. Pegman

在谷歌地圖上放置一個小人,小人會根據當前給予的方向指標持續地移動,直到碰到下一個方向指標,請問至少要改變幾個方向指標所指引的方向,滿足在任何一個小人位置都不會走到邊界外部。

由於放在非指標位置就不能移動,只需要考慮小人放在指標上,考慮每一個方向指標都指向另一個指標,這樣就能保證有數個循環,盡可能地挑選原方向。

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
#include <stdio.h>
#include <string.h>
#include <queue>
#include <functional>
#include <deque>
#include <algorithm>
using namespace std;
char sg[128][128];
int ig[128][128];
const int dx[] = {0, 0, 1, -1};
const int dy[] = {1, -1, 0, 0};
const char dd[] = "><v^";
int main() {
int testcase, n, m, cases = 0;
scanf("%d", &testcase);
while (testcase--) {
scanf("%d %d", &n, &m);
for (int i = 0; i < n; i++)
scanf("%s", sg[i]);
int idx = 0;
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
if (sg[i][j] != '.') {
ig[i][j] = idx++;
}
}
}
int match = 0, ret = 0;
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
if (sg[i][j] != '.') {
int odd = 0;
for (int k = 0; k < 4; k++)
if (dd[k] == sg[i][j])
odd = k;
int cost = 1, mm = 0;
for (int k = 0; k < 4; k++) {
int x = i, y = j;
x += dx[k], y += dy[k];
while (x < n && x >= 0 && y < m && y >= 0) {
if (sg[x][y] != '.') {
if (k == odd)
cost = 0;
mm = 1;
break;
}
x += dx[k], y += dy[k];
}
}
match += mm, ret += cost;
}
}
}
printf("Case #%d: ", ++cases);
if (match != idx)
puts("IMPOSSIBLE");
else
printf("%d\n", ret);
}
return 0;
}

B. Kiddie Pool

放滿游泳池的水,目標容積 $V$ 溫度 $X$,有 $N$ 種水源,每一種水源有各自的流速 $R$、溫度 $C$,水源可以同時放水,請問達到目標容積、溫度的最少時間為何。

當下直觀地沒考慮水源可以同時使用,卡了一陣子。

Linear Programming

發現可以同時運作,考慮二分最少時間,其中一種最簡單的應用線性規劃,判斷線性規劃方程式是否有解,python 內建 LP 也許很過分。自己曾經寫過 simplex,不過調校上相當痛苦,判斷有沒有解有困難。以下為 LP 模型,二分時間 $\text{limit_time}$

  1. $x_i \le \text{limit_time} * R[i]$
  2. $\sum{C_i x_i} = V X$
  3. $\sum x_i = V$
  4. $x_i \ge 0$

帶入 simplex algorithm,判斷四條方程式是否有解。代碼就不附,解不出來。

Greedy

另外一種方式,考慮限制 t 時間內能調配的最高溫 high_t、最低溫 low_t,這兩種溫度可以貪心法求得,接著考慮目標溫度 low_t <= X <= high_t,因為中間的溫度一定可解,具有連續性。

複雜度 $O(N \log 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
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 128;
const double eps = 1e-10;
double R[MAXN], C[MAXN], V, X;
int N;
vector< pair<double, double> > A;
int checkTime(double limitT) {
double v, t, low_t, high_t;
double mxV, teC;
v = t = 0;
for (int i = 0; i < N; i++) {
mxV = A[i].second * limitT; // flow in limit time
teC = A[i].first;
mxV = min(mxV, V - v);
t = (t * v + mxV * teC) / (v + mxV);
v += mxV;
if (v >= V - eps)
break;
}
if (v < V - eps)
return 0;
low_t = t;
v = t = 0;
for (int i = N-1; i >= 0; i--) {
mxV = A[i].second * limitT; // flow in limit time
teC = A[i].first;
mxV = min(mxV, V - v);
t = (t * v + mxV * teC) / (v + mxV);
v += mxV;
if (v >= V - eps)
break;
}
if (v < V - eps)
return 0;
high_t = t;
return X >= low_t - eps && X <= high_t + eps;
}
int main() {
int testcase, cases = 0;
scanf("%d", &testcase);
while (testcase--) {
scanf("%d %lf %lf", &N, &V, &X);
A.clear();
for (int i = 0; i < N; i++) {
scanf("%lf %lf", &R[i], &C[i]);
A.push_back(make_pair(C[i], R[i]));
}
sort(A.begin(), A.end());
double mxT, mnT;
mnT = A[0].first, mxT = A[N-1].first;
printf("Case #%d: ", ++cases);
if (X < mnT - eps || X > mxT + eps) {
puts("IMPOSSIBLE");
} else {
double l = 0, r = 1e+9, mid; // MAXV / MINR = 10000.0 / 0.0001
double ret = 0;
for (int it = 0; it < 256; it++) {
mid = (l + r)/2;
if (checkTime(mid))
r = mid, ret = mid;
else
l = mid;
}
printf("%.8lf\n", ret);
}
}
return 0;
}

Minkowski Sum

在此先將問題轉換成「求 1 秒內最多能湊出多少公升的 X 度水」,知道單位時間內的最多流量,就能貪心得到最短時間。

將每一種水源 $(R_i, C_i)$ 轉換成向量 $v_i = (R_i, R_i \times C_i)$,目標要在 $t$ 時間內,得到 $\sum{t_i v_i} = (V, V \times X)$,滿足所有的 $t_i \le t$

將問題壓縮成 $t_i \in \left \[ 0, 1 \right]$,將所有符合的 $\sum{t_i v_i}$ 疊加起來,得到的區域相當於在做 Minkowski Sum,區域是一個凸多邊形。接著在 $y = X x$ 尋找交集的最大 x 值。

Demo

上圖是一個 3 個水源的情況,假設要湊出溫度 $X = 0.5$,相當於找 $y = 0.5 x$,這三個向量在單位時間內的疊加為淡紅色區域。為了得到這淡紅色區域,使用極角排序,依序疊加可以得到下凸包,反過來疊加可以得到上凸包。

明顯地要找一個凸包跟一條線的交點,得到單位時間內的最大流量。此時的 x 軸表示流量、y 軸表示熱量,找到交集中最大的 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
#include <stdio.h>
#include <math.h>
#include <algorithm>
#include <set>
#include <vector>
using namespace std;
#define eps 1e-6
struct Pt {
double x, y;
int label;
Pt(double a = 0, double b = 0, int c = 0):
x(a), y(b), label(c) {}
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 {
if (fabs(x - a.x) > eps)
return x < a.x;
if (fabs(y - a.y) > eps)
return y < a.y;
return false;
}
};
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;
}
bool cmp(const Pt& p1, const Pt& p2)
{
if (p1.y == 0 && p2.y == 0 && p1.x * p2.x <= 0) return p1.x > p2.x;
if (p1.y == 0 && p1.x >= 0 && p2.y != 0) return true;
if (p2.y == 0 && p2.x >= 0 && p1.y != 0) return false;
if (p1.y * p2.y < 0) return p1.y > p2.y;
double c = cross2(p1, p2);
return c > 0 || (c == 0 && fabs(p1.x) < fabs(p2.x));
}
Pt getIntersect(Pt as, Pt ae, Pt bs, Pt be) {
Pt u = as - bs;
double t = cross2(be - bs, u)/cross2(ae - as, be - bs);
return as + (ae - as) * t;
}
int cmpZero(double v) {
if (fabs(v) > eps) return v > 0 ? 1 : -1;
return 0;
}
//
const int MAXN = 128;
int N;
double R[MAXN], C[MAXN], V, X;
void solveWithMinkowskiSum() {
vector<Pt> P;
for (int i = 0; i < N; i++)
P.push_back(Pt(R[i], R[i]*C[i]));
sort(P.begin(), P.end(), cmp); // solar sort
vector<Pt> convex;
double mxFlow = 0; // in unit time with temperature X
Pt u, v, o(0, 0), to(1, X); // y = (X) x, maximum x
u = v = Pt(0, 0);
convex.push_back(u);
for (int i = 0; i < N; i++) {
v = u + P[i];
u = v;
convex.push_back(v);
}
reverse(convex.begin(), convex.end());
u = v = Pt(0, 0);
for (int i = N-1; i >= 0; i--) {
v = u + P[i];
u = v;
convex.push_back(v);
}
for (int i = 0, j = (int) convex.size()-1; i < convex.size(); j = i++) {
u = convex[j], v = convex[i];
if (cmpZero(cross(o, to, u)) * cmpZero(cross(o, to, v)) < 0) {
Pt in = getIntersect(o, to, u, v);
mxFlow = max(mxFlow, in.x);
}
if (cmpZero(cross(o, to, v)) == 0)
mxFlow = max(mxFlow, v.x);
}
if (fabs(V) < eps)
printf("%.10lf\n", 0.0);
else if (fabs(mxFlow) < eps)
puts("IMPOSSIBLE");
else
printf("%.10lf\n", V / mxFlow);
}
int main() {
int testcase, cases = 0;
scanf("%d", &testcase);
while (testcase--) {
scanf("%d %lf %lf", &N, &V, &X);
V *= 10000, X *= 10000;
for (int i = 0; i < N; i++) {
scanf("%lf %lf", &R[i], &C[i]);
R[i] *= 10000, C[i] *= 10000;
}
printf("Case #%d: ", ++cases);
solveWithMinkowskiSum();
}
return 0;
}
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 +