UVa 10397 - Connect the Campus

Problem

在現有的鋪設下,還有幾處建築物之間沒有連通。

找到花費最小的歐基里德距離生成樹

Sample Input

1
2
3
4
5
6
7
8
9
10
11
12
13
14
4
103 104
104 100
104 103
100 100
1
4 2
4
103 104
104 100
104 103
100 100
1
4 2

Sample Output

1
2
4.41
4.41

Solution

直接用 kruskal 就可以完成,但是建造邊最慘將會達到 O(n^2),因此效率並不好,好幾年之後回過頭來寫這一題。

對於 EMST 可以使用 Delaunay triangulation 完成。Delaunay triangulation 原則上可以在 O(n log n) 時間內完成。由於 EMST 是 Delaunay 的 subgraph,而 Delaunay 保證 edge 數最多為 3n - 6。套用 kruskal 的 O(E log E) 算法就能保證在 O(n log n) 時間內完成。

Delaunay triangulation 目前實作有 D&C 和 random 兩種。

計算幾何資料請參照 morris821028/Computational-Geometry 下載。

廣泛的比較下,兩者期望都在 O(n log n) 完成,而實際結果 D&C 普遍速度快很多。random 作法很吃機率期望,原因是要支持動態的 point location 操作,沒有一個好的資料結構來保證每一步的搜索效率,教科書上給予一個類似可持久化結構的 DG structure,深度是期望的 O(log n),實作起來只會比 O(n) 快個常數倍。但 random increase algorithm 對於演算法的證明的確是比較容易,而且步驟相當好說明。

當效率不好去學 D&C algorithm,一開始要對座標進行排序,也就是不支持動態 (離線操作),接著針對 x 座標剖半,兩個完成 Delaunay triangulation 的凸包進行合併,接著需要將中間縫隙穿針引線地縫起來。因此要找到兩個凸包的下公切線,然後開始攀爬上去,保證一開始的下公切線一定是 Delaunay edge,然後開始窮舉下一個 Delaunay edge,過程中也不斷地移除不適的邊。

有相當不錯的資料結構可以快速地搜索公切線、圍繞凸包的頂點 … 等,但是根據教科書給予的計算,每一個點的 degree 期望值不超過 9,意即偷偷地窮舉攀爬不影響效率。

關於三點共圓,詢問一點是否在圓內的操作,投影到拋物面上,三點在投影結果上拉出一個平面,若點在圓內,則保證該點在平面的下方。求出外心計算,會造成誤差 (無法儲存除法運算結果),換成拋物面的思路魯棒性高。

下附 D&C algorithm 的做法,關於 random 就放在 github repo 封存了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
#include <stdio.h>
#include <math.h>
#include <algorithm>
#include <set>
#include <map>
#include <queue>
#include <vector>
#include <string>
#include <iostream>
#include <assert.h>
#include <string.h>
#include <list>
using namespace std;
#define eps 1e-8
#define MAXN (1048576)
#define MAXV 1024
struct Point {
double x, y;
int id;
Point(double a = 0, double b = 0, int c = -1):
x(a), y(b), id(c) {}
Point operator-(const Point &a) const {
return Point(x - a.x, y - a.y);
}
Point operator+(const Point &a) const {
return Point(x + a.x, y + a.y);
}
Point operator*(const double a) const {
return Point(x * a, y * a);
}
Point operator/(const double a) const {
return Point(x / a, y / a);
}
bool operator<(const Point &a) const {
if (fabs(x - a.x) > eps) return x < a.x;
if (fabs(y - a.y) > eps) return y < a.y;
return false;
}
bool operator==(const Point &a) const {
return fabs(x - a.x) < eps && fabs(y - a.y) < eps;
}
bool operator!=(const Point &a) const {
return !(fabs(x - a.x) < eps && fabs(y - a.y) < eps);
}
void read(int id = -1) {
this->id = id;
}
double dist(Point b) {
return hypot(x - b.x, y - b.y);
}
double dist2(Point b) {
return (x - b.x) * (x - b.x) + (y - b.y) * (y - b.y);
}
void print() {
printf("point (%lf, %lf)\n", x, y);
}
};
struct Point3D {
double x, y, z;
Point3D(double a = 0, double b = 0, double c = 0):
x(a), y(b), z(c) {}
Point3D(Point p) {
x = p.x, y = p.y, z = p.x * p.x + p.y * p.y;
}
Point3D operator-(const Point3D &a) const {
return Point3D(x - a.x, y - a.y, z - a.z);
}
double dot(Point3D a) {
return x * a.x + y * a.y + z * a.z;
}
};
struct Edge {
int id;
list<Edge>::iterator twin;
Edge(int id = 0) {
this->id = id;
}
};
int cmpZero(double v) {
if (fabs(v) > eps) return v > 0 ? 1 : -1;
return 0;
}
double cross(Point o, Point a, Point b) {
return (a.x-o.x)*(b.y-o.y) - (a.y-o.y)*(b.x-o.x);
}
Point3D cross(Point3D a, Point3D b) { // normal vector
return Point3D(a.y * b.z - a.z * b.y
, -a.x * b.z + a.z * b.x
, a.x * b.y - a.y * b.x);
}
int inCircle(Point a, Point b, Point c, Point p) {
if (cross(a, b, c) < 0)
swap(b, c);
Point3D a3(a), b3(b), c3(c), p3(p);
// printf("%lf %lf %lf\n", a3.x, a3.y, a3.z);
// printf("%lf %lf %lf\n", b3.x, b3.y, b3.z);
// printf("%lf %lf %lf\n", c3.x, c3.y, c3.z);
// printf("%lf %lf %lf\n", p3.x, p3.y, p3.z);
b3 = b3 - a3, c3 = c3 - a3, p3 = p3 - a3;
Point3D f = cross(b3, c3); // normal vector
return cmpZero(p3.dot(f)); // check same direction, in: < 0, on: = 0, out: > 0
}
int intersection(Point a, Point b, Point c, Point d) { // seg(a, b) and seg(c, d)
return cmpZero(cross(a, c, b)) * cmpZero(cross(a, b, d)) > 0
&& cmpZero(cross(c, a, d)) * cmpZero(cross(c, d, b)) > 0;
}
class Delaunay {
public:
list<Edge> head[MAXV]; // graph
Point p[MAXV];
int n, rename[MAXV];
void init(int n, Point p[]) {
for (int i = 0; i < n; i++)
head[i].clear();
for (int i = 0; i < n; i++)
this->p[i] = p[i];
sort(this->p, this->p + n);
for (int i = 0; i < n; i++)
rename[p[i].id] = i;
this->n = n;
divide(0, n - 1);
}
void addEdge(int u, int v) {
head[u].push_front(Edge(v));
head[v].push_front(Edge(u));
head[u].begin()->twin = head[v].begin();
head[v].begin()->twin = head[u].begin();
}
void divide(int l, int r) {
if (r - l <= 1) { // #point <= 2
for (int i = l; i <= r; i++)
for (int j = i+1; j <= r; j++)
addEdge(i, j);
return;
}
int mid = (l + r) /2;
divide(l, mid);
divide(mid + 1, r);
list<Edge>::iterator it;
int nowl = l, nowr = r;
// printf("divide %d %d\n", l, r);
for (int update = 1; update; ) { // find left and right convex, lower common tangent
update = 0;
Point ptL = p[nowl], ptR = p[nowr];
for (it = head[nowl].begin(); it != head[nowl].end(); it++) {
Point t = p[it->id];
double v = cross(ptR, ptL, t);
if (cmpZero(v) > 0 || (cmpZero(v) == 0 && ptR.dist2(t) < ptR.dist2(ptL))) {
nowl = it->id, update = 1;
break;
}
}
if (update) continue;
for (it = head[nowr].begin(); it != head[nowr].end(); it++) {
Point t = p[it->id];
double v = cross(ptL, ptR, t);
if (cmpZero(v) < 0 || (cmpZero(v) == 0 && ptL.dist2(t) < ptL.dist2(ptR))) {
nowr = it->id, update = 1;
break;
}
}
}
addEdge(nowl, nowr); // add tangent
// printf("add base %d %d\n", nowl, nowr);
for (int update = 1; true;) {
update = 0;
Point ptL = p[nowl], ptR = p[nowr];
int ch = -1, side = 0;
for (it = head[nowl].begin(); it != head[nowl].end(); it++) {
// ptL.print(), ptR.print(), p[it->id].print();
if (cmpZero(cross(ptL, ptR, p[it->id])) > 0
&& (ch == -1 || inCircle(ptL, ptR, p[ch], p[it->id]) < 0))
ch = it->id, side = -1;
// printf("test L %d %d %d\n", nowl, it->id, inCircle(ptL, ptR, p[ch], p[it->id]));
}
for (it = head[nowr].begin(); it != head[nowr].end(); it++) {
if (cmpZero(cross(ptR, p[it->id], ptL)) > 0
&& (ch == -1 || inCircle(ptL, ptR, p[ch], p[it->id]) < 0))
ch = it->id, side = 1;
// printf("test R %d %d %d\n", nowr, it->id, inCircle(ptL, ptR, p[ch], p[it->id]));
}
if (ch == -1) break; // upper common tangent
// printf("choose %d %d\n", ch, side);
if (side == -1) {
for (it = head[nowl].begin(); it != head[nowl].end(); ) {
if (intersection(ptL, p[it->id], ptR, p[ch])) {
head[it->id].erase(it->twin);
head[nowl].erase(it++);
} else
it++;
}
nowl = ch;
addEdge(nowl, nowr);
} else {
for (it = head[nowr].begin(); it != head[nowr].end(); ) {
if (intersection(ptR, p[it->id], ptL, p[ch])) {
head[it->id].erase(it->twin);
head[nowr].erase(it++);
} else
it++;
}
nowr = ch;
addEdge(nowl, nowr);
}
}
}
vector< pair<int, int> > getEdge() {
vector< pair<int, int> > ret;
list<Edge>::iterator it;
for (int i = 0; i < n; i++) {
for (it = head[i].begin(); it != head[i].end(); it++) {
if (it->id < i)
continue;
// printf("DG %d %d\n", i, it->id);
ret.push_back(make_pair(p[i].id, p[it->id].id));
}
}
return ret;
}
} tool;
struct edge {
int x, y, v;
edge(int a = 0, int b = 0, int c = 0):
x(a), y(b), v(c) {}
bool operator<(const edge &a) const {
return v < a.v;
}
};
int parent[1024], weight[1024];
void init(int n) {
for(int i= 0; i <= n; i++)
parent[i] = i, weight[i] = 1;
}
int findp(int x) {
return x == parent[x] ? x : (parent[x]=findp(parent[x]));
}
int joint(int x, int y) {
x = findp(x), y = findp(y);
if (x == y) return 0;
if(weight[x] > weight[y])
weight[x] += weight[y], parent[y] = x;
else
weight[y] += weight[x], parent[x] = y;
return 1;
}
int main() {
int n, m, cases = 0;
Point p[MAXV];
while (scanf("%d", &n) == 1) {
init(n);
int x, y;
int e = 0;
for (int i = 0; i < n; i++) {
scanf("%d %d", &x, &y);
p[i] = Point(x, y, i);
}
tool.init(n, p);
vector<edge> E;
vector< pair<int, int> > DG = tool.getEdge();
for (int i = 0; i < DG.size(); i++) {
x = DG[i].first, y = DG[i].second;
int v = (p[x].x - p[y].x) * (p[x].x - p[y].x) +
(p[x].y - p[y].y) * (p[x].y - p[y].y);
E.push_back(edge(x, y, v));
// printf("DG %d %d %d\n", x, y, v);
}
scanf("%d", &m);
for (int i = 0; i < m; i++) {
scanf("%d %d", &x, &y);
x--, y--;
e += joint(x, y);
}
sort(E.begin(), E.end());
double ret = 0;
for (int i = 0; i < E.size(); i++) {
x = E[i].x, y = E[i].y;
if (joint(x, y)) {
ret += sqrt(E[i].v);
e++;
if (e == n - 1)
break;
}
}
printf("%.2lf\n", ret);
}
return 0;
}
/*
2
1 1
2 2
0
1
1 1
0
4
103 104
104 100
104 103
100 100
1
4 2
4
3 4
4 0
4 3
0 0
1
4 2
*/
Read More +

UVa 1674 - Lightning Energy Report

Problem

給一棵樹,接著有數次操作將路徑 (u, v) 上的所有節點權重加上 k。

請問最後每一個點帶有的權重為何?

Sample Input

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

Sample Output

1
2
3
4
5
6
7
8
9
10
Case #1:
5
25
28
3
110
3
13
18
5

Solution

參照 zerojudge b322. 樹形鎖頭 的解法。

解說圖

A[] : 子樹總和
B[] : 節點權重

增加路徑 u 到 v 的權重為 k 時,路徑相當於 u 到某點 x 再到 LCA(u, v) 接著 y 最後 v。

那我們增加節點 u, v 的權重 B[u] += k, B[v] += k,減少其 LCA 的權重 B[LCA(u, v)] -= 2k,然後你會觀察 A[] 的部分,權重增加 k 的只會有 u-v 之間的所有節點。

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
#include <stdio.h>
#include <stdlib.h>
#include <algorithm>
#include <string.h>
#include <math.h>
#include <vector>
using namespace std;
#define MAXV 65536
int visited[MAXV];
vector<int> tree[MAXV];
int parent[MAXV], weight[MAXV];
int findp(int x) {
return parent[x] == x ? x : (parent[x] = findp(parent[x]));
}
int joint(int x, int y) {
x = findp(x), y = findp(y);
if(x == y)
return 0;
if(weight[x] > weight[y])
weight[x] += weight[y], parent[y] = x;
else
weight[y] += weight[x], parent[x] = y;
return 1;
}
// LCA
vector< pair<int, int> > Q[MAXV];// query pair, input index - node
int LCA[131072]; // [query time] input query answer buffer.
void tarjan(int u, int p) {// rooted-tree.
parent[u] = u;
for(int i = 0; i < tree[u].size(); i++) {//son node.
int v = tree[u][i];
if(v == p) continue;
tarjan(v, u);
parent[findp(v)] = u;
}
visited[u] = 1;
for(int i = 0; i < Q[u].size(); i++) {
if(visited[Q[u][i].second]) {
LCA[Q[u][i].first] = findp(Q[u][i].second);
}
}
}
int dfs(int u, int p, int weight[]) {
int sum = weight[u];
for(int i = 0; i < tree[u].size(); i++) {//son node.
int v = tree[u][i];
if(v == p) continue;
sum += dfs(v, u, weight);
}
return weight[u] = sum;
}
int main() {
// freopen("in.txt", "r+t", stdin);
// freopen("out2.txt", "w+t", stdout);
int n, m, x, y;
int testcase, cases = 0;
int X[MAXV], Y[MAXV], K[MAXV];
scanf("%d", &testcase);
while (testcase--) {
scanf("%d", &n);
for (int i = 0; i < n; i++)
tree[i].clear();
for (int i = 1; i < n; i++) {
scanf("%d %d", &x, &y);
tree[x].push_back(y);
tree[y].push_back(x);
}
int weight[MAXV] = {}, extra[MAXV] = {};
for (int i = 0; i < n; i++)
visited[i] = 0, Q[i].clear();
scanf("%d", &m);
for (int i = 0; i < m; i++) {
scanf("%d %d %d", &X[i], &Y[i], &K[i]);
Q[X[i]].push_back(make_pair(i, Y[i]));
Q[Y[i]].push_back(make_pair(i, X[i]));
}
tarjan(0, -1);
for (int i = 0; i < m; i++) {
extra[LCA[i]] += K[i];
weight[X[i]] += K[i];
weight[Y[i]] += K[i];
weight[LCA[i]] -= 2 * K[i];
}
dfs(0, -1, weight);
printf("Case #%d:\n", ++cases);
for (int i = 0; i < n; i++)
printf("%d\n", weight[i] + extra[i]);
}
return 0;
}
Read More +

UVa 1659 - Help Little Laura

Problem

平面上有 N 個點,並且給定數條有向邊,在這個遊戲中,必須從某個點出發,沿著邊回到起點。每一條邊最多經過一次,而點可以重複走。

每經過一條邊,可以獲益每單位長 X 的利益,但是使用一條邊的成本為 Y。

請問最大獲益為何?

Sample Input

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
4 5 1
0 0 2 3 0
1 0 3 4 0
1 1 4 0
0 1 1 0
1 2 1
0 0 0
10 7 2
0 0 2 4 0
5 0 3 0
5 10 4 10 0
2 3 5 0
7 5 6 0
0 11 1 0
8 0 10 5 0
18 3 7 0
14 5 8 1 0
12 9 9 0
0

Sample Output

1
2
3
Case 1: 16.00
Case 2: 0.00
Case 3: 522.18

Solution

目標將每一個點調整 indeg = outdeg,要將多餘的邊捨去,只要滿足 indeg = outdeg,則可以表示平面上有數個尤拉環道。

接著建造最少費用流的模型,來捨去掉多餘的邊。當存有邊 e(i->j) 時,outdeg[i]++, indeg[j]++,一開始選擇所有 cost(e(i->j)) > 0 的邊,作為初始盤面,對於 cost(e(i->j)) < 0 先不選,選擇獲益為負的結果並不好。

如果 cost(e(i->j)) > 0,建造邊 i->j, flow: 1, cost: cost(e(i->j)),假使在最少費用流中選擇這一條邊時,就會將 deg 數量減少。對於 cost(e(i->j)) < 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
#include <stdio.h>
#include <string.h>
#include <math.h>
#include <queue>
#include <functional>
#include <deque>
#include <algorithm>
using namespace std;
#define eps 1e-8
struct Node {
int x, y, cap;
double cost;// x->y, v
int next;
} edge[1000005];
int e, head[600], prev[600], record[600], inq[600];
double dis[600];
void addEdge(int x, int y, int cap, double cost) {
edge[e].x = x, edge[e].y = y, edge[e].cap = cap, edge[e].cost = cost;
edge[e].next = head[x], head[x] = e++;
edge[e].x = y, edge[e].y = x, edge[e].cap = 0, edge[e].cost = -cost;
edge[e].next = head[y], head[y] = e++;
}
double mincost(int s, int t, int n) {
double mncost = 0;
int flow, totflow = 0;
int i, x, y;
double oo = 1e+30;
while(1) {
for (int i = 0; i <= n; i++)
dis[i] = oo;
dis[s] = 0;
deque<int> Q;
Q.push_front(s);
while(!Q.empty()) {
x = Q.front(), Q.pop_front();
inq[x] = 0;
for(i = head[x]; i != -1; i = edge[i].next) {
y = edge[i].y;
if(edge[i].cap > 0 && dis[y] > dis[x] + edge[i].cost) {
dis[y] = dis[x] + edge[i].cost;
prev[y] = x, record[y] = i;
if(inq[y] == 0) {
inq[y] = 1;
if(Q.size() && dis[Q.front()] > dis[y])
Q.push_front(y);
else
Q.push_back(y);
}
}
}
}
if(dis[t] == oo)
break;
flow = 0x3f3f3f3f;
for(x = t; x != s; x = prev[x]) {
int ri = record[x];
flow = min(flow, edge[ri].cap);
}
for(x = t; x != s; x = prev[x]) {
int ri = record[x];
edge[ri].cap -= flow;
edge[ri^1].cap += flow;
edge[ri^1].cost = -edge[ri].cost;
}
totflow += flow;
mncost += dis[t] * flow;
}
return mncost;
}
int main() {
int cases = 0;
int N, X, Y;
int x[128], y[128], u;
while (scanf("%d %d %d", &N, &X, &Y) == 3 && N) {
vector<int> g[128];
for (int i = 1; i <= N; i++) {
scanf("%d %d", &x[i], &y[i]);
while (scanf("%d", &u) == 1 && u)
g[i].push_back(u);
}
e = 0;
memset(head, -1, sizeof(head));
int deg[128] = {};
double sumCost = 0;
for (int i = 1; i <= N; i++) {
for (int j = 0; j < g[i].size(); j++) {
u = g[i][j];
double d = hypot(x[i] - x[u], y[i] - y[u]);
double cost = d * X - Y;
if (cost < 0) {
addEdge(u, i, 1, -cost);
} else {
sumCost += cost;
addEdge(i, u, 1, cost);
deg[u]--, deg[i]++;
}
}
}
int source = 0, sink = N + 1;
for (int i = 1; i <= N; i++) {
if (deg[i] > 0) addEdge(source, i, deg[i], 0);
if (deg[i] < 0) addEdge(i, sink, -deg[i], 0);
}
double mncost = mincost(source, sink, N+1);
printf("Case %d: %.2lf\n", ++cases, sumCost - mncost + eps);
}
return 0;
}
/*
4 5 1
0 0 2 3 0
1 0 3 4 0
1 1 4 0
0 1 1 0
1 2 1
0 0 0
10 7 2
0 0 2 4 0
5 0 3 0
5 10 4 10 0
2 3 5 0
7 5 6 0
0 11 1 0
8 0 10 5 0
18 3 7 0
14 5 8 1 0
12 9 9 0
0
*/
Read More +

UVa 1649 - Binomial coefficients

Problem

組合的反函數,現在給定組合數,輸出是由哪幾種組合可以配出組合數。

Sample Input

1
2
3
2
2
15

Sample Output

1
2
3
4
1
(2,1)
4
(6,2) (6,4) (15,1) (15,14)

Solution

先預見表,完成前幾項的結果。

看著巴斯卡三角形單純看 i,對於 C(n, i) 是一個嚴格遞增的函數。因此窮舉 i,針對組合數進行二分搜索。

為防止越界,在整數計算上可以利用除法進行比大小。

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
#include <stdio.h>
#include <map>
#include <vector>
#include <algorithm>
using namespace std;
#define MAXN 1024
const long long MAXVAL = 1e+16, MAXVAL2 = 1e+15;
long long C[MAXN][MAXN] = {};
map<long long, vector< pair<long long, long long> > > R;
int testCombination(long long n, long long m, long long Cnm) { // test C(n, m), m < 10
long long ret = 1;
m = min(m, n-m);
for (long long i = 1; i <= m; i++) {
if (Cnm * i / (n + 1 - i) / ret < 1)
return 1;
ret = ret * (n + 1 - i) / i;
}
return ret == Cnm ? 0 : -1;
}
vector< pair<long long, long long> > invCombination(long long n) {
vector< pair<long long, long long> > ret;
ret = R[n];
for (int i = 1; i < 10; i++) {
long long l = 1, r = n, mid;
while (l <= r) {
mid = (l + r)/2;
int f = testCombination(mid, i, n); // {-1, 0, 1}
if (f == 0) {
ret.push_back(make_pair(mid, i));
ret.push_back(make_pair(mid, mid - i));
break;
}
if (f < 0)
l = mid + 1;
else
r = mid - 1;
}
}
sort(ret.begin(), ret.end());
ret.resize(unique(ret.begin(), ret.end()) - ret.begin());
return ret;
}
int main() {
// printf("%d\n", testCombination(5000, 2, 12497500));
C[0][0] = 1;
for (int i = 1; i < MAXN; i++) {
C[i][0] = 1;
for (int j = 1; j <= i; j++) {
C[i][j] = C[i-1][j-1] + C[i-1][j];
C[i][j] = min(C[i][j], MAXVAL);
if (j != i && C[i][j] <= MAXVAL2)
R[C[i][j]].push_back(make_pair(i, j));
}
}
// for (int i = 0; i < 10; i++)
// printf("%lld\n", C[MAXN-1][i]);
int testcase;
long long n;
scanf("%d", &testcase);
while (testcase--) {
scanf("%lld", &n);
vector< pair<long long, long long> > r = invCombination(n);
printf("%d\n", (int) r.size());
for (int i = 0; i < r.size(); i++)
printf("(%lld,%lld)%c", r[i].first, r[i].second, i == r.size()-1 ? '\n' : ' ');
}
return 0;
}
Read More +

UVa 1614 - Hell on the Markets

Problem

金融交易,它們希望賣出、買進交易結束後的差為零。

意即將數字分兩堆的結果相同。

Sample Input

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

Sample Output

1
2
3
No
Yes
1 -1 -1 1

Solution

由於保證 0 < a[i] <= i,基於這一點,可以發現到只考慮一個數字 a[1] 時,必然可以湊出 1 - a[1],當考慮第 i 大數字時,保證前 i - 1 個數字可以湊出 1 ~ (i-1),加上第 i 個數字必然可以湊出 1 ~ (i-1)+a[i] (1, 2, 3, ..., i-1, a[i], 1+a[i], ..., i-1+a[i])。

而事實上,可以湊出所有的 1 ~ sum[i],根據遞歸是可以證明的。只要總和是偶數,必然可以劃分成總和相同的兩堆。排序一下,從大的數字開始挑,貪心去完成目標的總和。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#include <stdio.h>
#include <algorithm>
using namespace std;
pair<int, int> A[100000];
int main() {
int n;
while (scanf("%d", &n) == 1) {
long long sum = 0;
int ret[100000] = {};
for (int i = 0; i < n; i++) {
scanf("%d", &A[i].first);
A[i].second = i;
sum += A[i].first;
}
if (sum%2) {
puts("No");
} else {
puts("Yes");
sum /= 2;
sort(A, A+n);
for (int i = n-1; i >= 0; i--) {
if (sum >= A[i].first)
sum -= A[i].first, ret[A[i].second] = 1;
else
ret[A[i].second] = -1;
}
for (int i = 0; i < n; i++)
printf("%d%c", ret[i], i == n-1 ? '\n' : ' ');
}
}
return 0;
}
Read More +

UVa 1169 - Robotruck

Problem

在一個二維平面中,灑落 N 個垃圾,機器人從原點 (0, 0) 依序要蒐集垃圾,機器人一次承載 W 重量的垃圾,隨時可以返回原點將垃圾放下。

請問依序收集垃圾的最少花費為何?

Sample Input

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

Sample Output

1
14

Solution

一開始得到方程式 dp[i] = min(dp[j-1] + cost[j, i] + dist[j] + dist[i]) 必須滿足 w[j, i] <= C,其中 j <= i。

其中 dp[i] 表示依序收集前 i 個垃圾的最小花費,cost[j, i] 表示走訪編號 j 到 i 垃圾位置的花費,補上原點到編號 j,並且收集完從 i 回到原點。

這樣的方程式轉換需要 N^2 時間,化簡一下式子

1
2
3
dp[i] = dp[j-1] + cost[j, i] + dist[j] + dist[i]
dp[i] = dp[j-1] + cost[i] - cost[j] + dist[j] + dist[i]
dp[i] = (dp[j-1] - cost[j] + dist[j]) + cost[i] + dist[i]

會發現 i, j 獨立,也滿足垃圾重量累計遞增,因此盡可能保留 dp[j-1] - cost[j] + dist[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
#include <stdio.h>
#include <stdlib.h>
#include <queue>
#include <algorithm>
using namespace std;
#define MAXN 131072
int x[MAXN], y[MAXN], w[MAXN];
int cost[MAXN], dist[MAXN];
int dp[MAXN];
int f(int j) {
return dp[j-1] - cost[j] + dist[j];
}
int main() {
int testcase, C, N;
scanf("%d", &testcase);
while (testcase--) {
scanf("%d %d", &C, &N);
w[0] = 0, cost[0] = 0;
for (int i = 1; i <= N; i++) {
scanf("%d %d %d", &x[i], &y[i], &w[i]);
cost[i] = cost[i-1] + abs(x[i] - x[i-1]) + abs(y[i] - y[i-1]);
dist[i] = abs(x[i]) + abs(y[i]);
w[i] += w[i-1];
}
// dp[i] = dp[j-1] + cost[j, i] + dist[j] + dist[i]
// dp[i] = dp[j-1] + cost[i] - cost[j] + dist[j] + dist[i]
// dp[i] = (dp[j-1] - cost[j] + dist[j]) + cost[i] + dist[i]
deque<int> Q;
Q.push_back(0);
for (int i = 1; i <= N; i++) {
while (!Q.empty() && w[i] - w[Q.front()] > C)
Q.pop_front();
dp[i] = f(Q.front() + 1) + cost[i] + dist[i];
while (!Q.empty() && f(Q.back() + 1) >= f(i + 1))
Q.pop_back();
Q.push_back(i);
}
printf("%d\n", dp[N]);
if (testcase) puts("");
}
return 0;
}
Read More +

UVa 10907 - Art Gallery

Problem

給一個簡單多邊形,內部放置一點求可視範圍的區域面積。

Sample Input

1
2
3
4
5
6
7
8
9
5
0 0
50 50
100 0
100 100
0 100
2
49 50
50 51

Sample Output

1
2
3
Gallery #1
6250.00
7500.00

Solution

一開始以為可視範圍可能保證是凸多邊形,後來發現其實不一定,有可能是簡單多邊形。

錯誤的思路-一開始想用半平面交計算,所以一直掛在特殊 case 下。

首先,對詢問點將每一個點拿來作極角排序,然後掃描相鄰的極角,對於每一個相鄰極角會與詢問點拉出一個小角度,兩邊的射線無限延伸,接著保證簡單多邊形的每一條邊要不橫跨這個張角、要不完全沒有,不會存在一小段於張角中。

因此會發現會有很多三角形,取交集拿到最靠近詢問點的三角形,兩條射線上取最靠近詢問點的座標,但是這樣的三角形不保證一定在三角形內部,把最後得到的三角形每條邊上的中點拿來做測試,使用射線法檢查是否在簡單多邊形內部。

抓思路 BUG 就花一個早上 Orz。

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
#include <stdio.h>
#include <math.h>
#include <algorithm>
#include <set>
#include <vector>
using namespace std;
#define eps 1e-6
#define MAXN 131072
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 {
if (fabs(x - a.x) > eps)
return x < a.x;
if (fabs(y - a.y) > eps)
return y < a.y;
return false;
}
bool operator==(const Pt &a) const {
return fabs(x - a.x) < eps && fabs(y - a.y) < eps;
}
};
double dist(Pt a, Pt b) {
return hypot(a.x - b.x, a.y - b.y);
}
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;
double angle;
int label;
Seg(Pt a = Pt(), Pt b = Pt(), int l=0):s(a), e(b), label(l) {
angle = atan2(e.y - s.y, e.x - s.x);
}
bool operator<(const Seg &other) const {
if (fabs(angle - other.angle) > eps)
return angle > other.angle;
if (cross(other.s, other.e, s) > -eps)
return true;
return false;
}
};
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;
}
int intersection(Pt as, Pt at, Pt bs, Pt bt) {
if (onSeg(as, at, bs) || onSeg(as, at, bt) ||
onSeg(bs, bt, as) || onSeg(bs, bt, at))
return 1;
if(cross(as, at, bs) * cross(as, at, bt) < 0 &&
cross(at, as, bs) * cross(at, as, bt) < 0 &&
cross(bs, bt, as) * cross(bs, bt, at) < 0 &&
cross(bt, bs, as) * cross(bt, bs, at) < 0)
return 1;
return 0;
}
int inPolygon(Pt p[], int n, Pt q) {
int i, j, cnt = 0;
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;
}
const double pi = acos(-1);
double artGallery(Pt p[], int n, Pt q) { // polygon: anti-clockwise order
vector< pair<double, Pt> > A;
for (int i = 0; i < n; i++) {
if (!(p[i] == q))
A.push_back(make_pair(atan2(p[i].y - q.y, p[i].x - q.x), p[i]));
}
sort(A.begin(), A.end());
double ret = 0;
int m = A.size();
for (int i = 0, j = m-1; i < m; j = i++) {
if (fabs(A[i].first - A[j].first) > eps) {
vector<Seg> segs;
Pt a, b, ta, tb;
a = q + (A[j].second - q) * 10000;
b = q + (A[i].second - q) * 10000;
for (int k = 0, l = n-1; k < n; l = k++) {
if ((cross(q, A[j].second, p[k]) * cross(q, A[j].second, p[l]) < eps
&& cross(q, A[i].second, p[k]) * cross(q, A[i].second, p[l]) < eps)) {
if (p[l] == q || p[k] == q) continue;
if (intersection(a, q, p[l], p[k]) && intersection(b, q, p[l], p[k]) && !onSeg(p[l], p[k], q))
segs.push_back(Seg(p[l], p[k]));
}
}
// printf("base %lf %lf %lf %lf\n", A[i].second.x, A[i].second.y, A[j].second.x, A[j].second.y);
for (int i = 0; i < segs.size(); i++) {
// printf("%lf %lf %lf %lf\n", segs[i].s.x, segs[i].s.y, segs[i].e.x, segs[i].e.y);
if (intersection(a, q, segs[i].s, segs[i].e)) {
ta = getIntersect(Seg(a, q), segs[i]);
tb = getIntersect(Seg(b, q), segs[i]);
if (dist(ta, q) < dist(a, q) && !(a == q))
a = ta;
if (dist(tb, q) < dist(b, q) && !(b == q))
b = tb;
}
}
if (inPolygon(p, n, Pt((a.x + b.x)/2, (a.y + b.y)/2)) &&
inPolygon(p, n, Pt((a.x + q.x)/2, (a.y + q.y)/2)) &&
inPolygon(p, n, Pt((q.x + b.x)/2, (q.y + b.y)/2))) {
ret += fabs(cross(q, a, b))/2;
// puts("Y");
}
// printf("---- %lf %lf %lf %lf\n", a.x, a.y, b.x, b.y);
// puts("--");
}
}
return ret;
}
int main() {
int testcase, cases = 0;
int n, m;
double x, y;
Pt D[105];
while(scanf("%d", &n) == 1) {
for (int i = 0; i < n; i++)
scanf("%lf %lf", &D[i].x, &D[i].y);
scanf("%d", &m);
printf("Gallery #%d\n", ++cases);
for (int i = 0; i < m; i++) {
scanf("%lf %lf", &x, &y);
double area = artGallery(D, n, Pt(x, y));
printf("%.2lf\n", area);
}
}
return 0;
}
/*
5
0 0
50 50
100 0
100 100
0 100
2
49 50
50 51
7
0 5
5 0
10 7
15 0
20 5
15 10
5 10
7
0 5
5 0
10 7
15 0
20 5
15 10
5 10
*/
Read More +

UVa 12865 - Volume Control

Problem

1, 2, 4, 7, 10, 15, 19, 26, 31, 37, 43, 54, 60, 73

A027384 Number of distinct products ij with 0 <= i, j <= n.

詢問 0 到 N 的連續整數,任兩個挑出來相乘,有多少不同的整數。

Sample Input

1
2
3
2
3
4

Sample Output

1
2
7
10

Solution

聽說當下比賽很多人直接本地建表,不過這一題還真的不知道該怎麼解比較好。

用了窮舉的方式,加入第 i 個整數,將與 0 ~ i 相乘,這時候增加多少不同整數?

  1. 找到 i * j == p * q 其中滿足 p, q < i,要忽略所有可能的 j,剩餘的結果就是增加的數量。
  2. 假設 i = a * b, p = a * ?1, q = b * ?2
  3. 得到 j = ?1 * ?2?2 < a, ?1 < b

用嚴格增加的趨勢,依序窮舉 i 的因數 a (小到大),?2 可以帶入的數字會遞增,而 ?1 會遞減,而中間會有一段重複的窮舉,只考慮 ?2 進行遞增即可。將不好的 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
#include <stdio.h>
#include <set>
#include <math.h>
#include <vector>
using namespace std;
int main() {
int ret[65536] = {1}, sum = 1;
int nop[32767] = {}, cases = 0;
for (int i = 1; i <= 30000; i++) {
vector<int> f;
int first_prime;
for (int j = 2; j * j <= i; j++) {
if (i%j == 0) {
f.push_back(j);
}
}
// i * j == p * q (p, q < i)
// i = a * b, p = a * ?1, q = b * ?2
// j = ?1 * ?2
cases++;
first_prime = f.size() ? f[0] : i;
for (int j = 0, q1, q2 = 2; j < f.size(); j++) { // f[j] x (i / f[j])
for (; q2 < f[j]; q2++) { // ?2 < a, ?1 < b
for (q1 = i/ f[j] - 1; q1 >= 1; q1--)
nop[q1 * q2] = cases;
}
}
for (int j = i / first_prime; j <= i; j++)
sum += nop[j] != cases;
ret[i] = sum;
}
int testcase, n;
scanf("%d", &testcase);
while (testcase--) {
scanf("%d", &n);
printf("%d\n", ret[n]);
}
return 0;
}
Read More +

UVa 12863 - Journey through the kingdom

Problem

對於每一個點 (i, j) 可以護送人的範圍為中間張開矩形 [i-r: i+r] x [j - c: j+c],可以傳送到矩形任何一點花費為 V(i, j)。每個點的花費和矩形大小都可以不同。

Sample Input

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

Sample Output

1
3 -1 1 0

Solution

什麼,居然有 range tree 配上 dijkstra?

很明顯是一題最短路徑。所有點都分配在 N x M 上,最慘為 25 萬個點,同時邊最慘邊數量也就是 25 萬 x 25 萬,因此在 O(E log V) 肯定是過不去的。

但是後來發現到,花費是在點上,因此可以換個角度想,每次更新離開該點花費最小的,也就是說每 pop 一個點,就能知道有一個矩形內保證是最短路,不會再進行任何更新。因此只要不斷地找到區域內尚未走訪的點,將其移除即可。

為了能夠快速找到矩形內尚未走訪的點,最好的方式是使用 range tree,但是找到區域內所有點的成本是 O(log^2 n + k),用一堆 set<int> 串 list 起來也要 O(r log n + k),這種作法會 TLE。而標程中使用 BIT 完成 range tree 的概念,BIT 查找速度是挺快的,但是看起來比較接近 O(k log^2 n),只能說點移除的速度很快,所以 k 小很多。

用 2D BIT 作為 range tree 的基底,也要確保每一個點很緊密地在 R x C 的每一格,range tree 找到區域內所有尚未走訪的點,進行 dijkstra 更新。

自己寫一個樹套樹的作法嘗試輾輾看,但是在建表上就已經快崩,記憶體飛漲得相當快,導致速度被拉下。之後改用一個 list 串出一堆一條一條的 set,這樣的速度,隨後用二分處理,比用 2D BIT 建造的 range tree 還要快,至少在隨機測資中速度是兩三倍以上,上傳 TLE。

看來只能乖乖地標程給的 … WTF,居然就這樣屈服了,都寫了好幾個版本。就不能讓我混過嗎。QAQ

當前使用優化策略: 建立於 dijkstra

  • 方案 1:
    (1) 啟發式搜索,相同花費下鄰近目標地的優先擴充,沒有好的估價。
    (2) 對於矩形,每個 row 當作一條,建立 list,把不可能需要更新的點移除,實作由 set row[MAXN] 完成。
  • 方案 2:
    (1) 啟發式搜索,相同花費下鄰近目標地的優先擴充,沒有好的估價。
    (2) 對於矩形,random 矩形內部 K 個點。
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
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <queue>
#include <set>
#include <algorithm>
using namespace std;
#define MAXN 512
int V[MAXN][MAXN], R[MAXN][MAXN], C[MAXN][MAXN];
int TX, TY;
struct Node {
int r, c, v, h;
Node(int a=0, int b=0, int d=0, int e=0):
r(a), c(b), v(d), h(e) {}
bool operator<(const Node &x) const {
if (v != x.v)
return v > x.v;
return h > x.h;
}
};
priority_queue<Node> pQ; // dijkstra
struct RangeTree { // 2D binary indexed tree
int A[MAXN][MAXN];
int R, C;
void init(int R, int C) {
this->R = R, this->C = C;
memset(A, 0, sizeof(A));
for (int i = 1; i <= R; i++)
for (int j = 1; j <= C; j++)
modify(i, j, 1);
}
void modify(int x, int y, int val) {
for (; x <= R; x += x&(-x)) {
for (int i = y; i <= C; i += i&(-i))
A[x][i] += val;
}
}
int query(int x, int y) {
int ret = 0;
for (; x > 0; x -= x&-x)
for (int i = y; i > 0; i -= i&(-i))
ret += A[x][i];
return ret;
}
int rectSum(int lx, int rx, int ly, int ry) {
return query(rx, ry) - query(lx-1, ry) - query(rx, ly-1) + query(lx-1, ly-1);
}
void update(int lx, int rx, int ly, int ry, int val, int tot) { // {val: update cost, tot: #unvisited point in area.}
if (tot == -1)
tot = rectSum(lx, rx, ly, ry);
if (tot == 0) return;
if (lx == rx) {
if (ly == ry) {
pQ.push(Node(lx, ly, val + V[lx][ly], abs(lx-TX) + abs(ly-TY)));
modify(lx, ly, -1);
return;
}
int cnt = rectSum(lx, rx, ly, (ly + ry)/2);
if (cnt)
update(lx, rx, ly, (ly + ry)/2, val, cnt);
if (cnt < tot)
update(lx, rx, (ly + ry)/2 + 1, ry, val, tot - cnt);
}
else {
int cnt = rectSum(lx, (lx + rx)/2, ly, ry);
if (cnt)
update(lx, (lx + rx)/2, ly, ry, val, cnt);
if (cnt < tot)
update((lx + rx)/2 + 1, rx, ly, ry, val, tot - cnt);
}
}
} rangeTree;
int findPath(int n, int m, int sx, int sy, int ex, int ey) {
if (sx == ex && sy == ey) return 0;
TX = ex, TY = ey;
rangeTree.init(n, m);
rangeTree.modify(sx, sy, -1);
while (!pQ.empty()) pQ.pop();
pQ.push(Node(sx, sy, V[sx][sy], abs(sx-ex) + abs(sy-ey)));
Node u;
int lr, rr, lc, rc;
while (!pQ.empty()) {
u = pQ.top(), pQ.pop();
if (abs(u.r - ex) <= R[u.r][u.c] && abs(u.c - ey) <= C[u.r][u.c])
return u.v;
lr = max(1, u.r - R[u.r][u.c]), rr = min(n, u.r + R[u.r][u.c]);
lc = max(1, u.c - C[u.r][u.c]), rc = min(m, u.c + C[u.r][u.c]);
rangeTree.update(lr, rr, lc, rc, u.v, -1);
}
return -1;
}
int main() {
// freopen("in.txt", "r+t", stdin);
// freopen("out.txt", "w+t", stdout);
int n, m, q, X[16], Y[16];
while (scanf("%d %d %d", &n, &m, &q) == 3) {
for (int i = 1; i <= n; i++)
for (int j = 1; j <= m; j++)
scanf("%d", &V[i][j]);
for (int i = 1; i <= n; i++)
for (int j = 1; j <= m; j++)
scanf("%d", &R[i][j]);
for (int i = 1; i <= n; i++)
for (int j = 1; j <= m; j++)
scanf("%d", &C[i][j]);
for (int i = 0; i < q; i++)
scanf("%d %d", &X[i], &Y[i]);
for (int i = 1; i < q; i++) {
int r = findPath(n, m, X[i-1], Y[i-1], X[i], Y[i]);
printf("%d%c", r, i == q - 1 ? '\n' : ' ');
}
}
return 0;
}
/*
3 4 5
1 2 1 1
1 5 3 4
1 1 6 3
1 2 3 3
3 3 1 2
0 0 0 1
1 4 0 1
2 3 0 1
4 1 3 1
1 1
3 4
1 1
2 2
2 2
*/
Read More +

UVa 10154 - Weights and Measures

曾經紅及一時的的烏龜塔問題,討論相當多。最近學弟因為 2014 年資訊奧林匹亞研習營初選中的一題,跑來問我怎麼解比較好。但是想一想之前都是用 dp 過去的。速度 O(n^2),優化也只會是 O(k n),k 是答案,還是還用了貪心去解決,但是後來才挖到這個化石,想法是挺簡單的,人生白活了。

l大所寫的演算法是有來源的。
寄信詢問l大之後,得到的回覆,整理於下。

最早出現的文獻

Moore, J.M.(1968) An n job, one machine sequencing algorithm for minimizing the number of late jobs. Management Science. 15(1):102-109.

現在的演算法名稱 Moore-Hodgson Algorithm 時間複雜度 O(N * log(N)) 演算法證明

  1. 先證至少有一最佳解工作順序是依完工期限由小到大排

  2. 證明拿掉最大執行時間的工作不會比最佳解差 問題轉換方式

Instance:

  • 有n個工作要完成 <—> 有n個箱子要疊
  • 每個工作有不同的處理時間 <—> 不同的重量
  • 每個工作有不同的完工期限 <—> 不同的載重量(要包含自己重量)

Question: 讓無法如期完工的工作越少越好 <—> 箱子疊越多越好 C++實作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
struct Job {int time, due;} job[10];
bool cmp(const Job& j1, const Job& j2) {
return j1.due < j2.due;
}
void Moore_Hodgson() {
sort(job, job+10, cmp);
int t = 0;
priority_queue<int> pq;
for (int i=0; i<10; ++i) {
pq.push(job[i].time);
t += job[i].time;
if (t > job[i].due) t -= pq.top(), pq.pop();
}
cout << "如期完成的工作(箱子)數目,最多為" << pq.size();
}
Read More +