UVa 953 - The Incredible Pile Machine

Problem

現在有 N 堆,總共有 N 種物品,希望移動最少次,使得每一堆只具有單一種物品。

輸出最小的組合方案。按照第 i 種物品放置在第 j 堆的字典順序最小。

Sample Input

1
2
3
4
5
6
7
4
3 2 3 4 0 0 2 1 3 1
3 66 66 0 66 66 0 66 66 66
6 476 357 874 50 594 394 320 803 817 348 252 940 453 500 647 299
94 143 800 947 561 885 389 172 301 276 612 130 540 731 774 306 96
239 227 907
2 99 1 1 99

Sample Output

1
2
3
4
021 9
012 264
251340 12741
01 2

Solution

很明顯地答案會是在某一堆 X 指派給 A 物品,那麼可以減少的移動次數是 X 原本具有 A 的數量。因此答案會是物品述兩總和扣除最大權匹配。

但是答案特別要求輸出字典順序要最小,所以用 KM 算法、最少費用流會稍微麻煩一點,也許多做 $O(N^2)$ 次的窮舉也可以完成。這裡使用 bitmask 的方式去找到最順序最小的最大權匹配。

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 <string.h>
#include <vector>
#include <algorithm>
using namespace std;
const int MAXN = 10;
int dp[MAXN][1<<MAXN], used[MAXN][1<<MAXN];
int main() {
int testcase, N;
int type[20][20];
scanf("%d", &testcase);
while (testcase--) {
scanf("%d", &N);
int sum = 0;
for (int i = 0; i < N; i++) {
for (int j = 0; j < N; j++)
scanf("%d", &type[i][j]), sum += type[i][j];
}
memset(dp, 0, sizeof(dp));
memset(used, 0, sizeof(used));
for (int i = 0; i < N; i++)
dp[0][1<<i] = type[0][i];
for (int i = 0; i < N; i++) {
for (int j = 0; j < (1<<N); j++) {
for (int k = 0; k < N; k++) {
if ((j>>k)&1) continue;
dp[i+1][j|(1<<k)] = max(dp[i+1][j|(1<<k)], dp[i][j] + type[i+1][k]);
}
}
}
used[N-1][(1<<N) - 1] = 1;
for (int i = N-2; i >= 0; i--) {
for (int j = 0; j < (1<<N); j++) {
for (int k = 0; k < N; k++) {
if ((j>>k)&1) continue;
if (used[i+1][j|(1<<k)] && dp[i+1][j|(1<<k)] == dp[i][j] + type[i+1][k])
used[i][j] = 1;
}
}
}
int ret = dp[N-1][(1<<N)-1];
for (int i = 0, p, q = 0; i < N; i++) {
for (int j = 0; j < N; j++) {
if ((q>>j)&1)
continue;
if (used[i][q|(1<<j)]) {
p = j, q |= 1<<j;
break;
}
}
printf("%d", p);
}
printf(" %d\n", sum - ret);
}
return 0;
}
Read More +

UVa 905 - Tacos Panchita

Problem

類似磁性場,所有的指針位置都會指向目的地。不過這題的感覺比較像是風向儀,從目的地往外吹動,三角形是風向儀所在的位置,正方形則表示旗幟飄揚的位置。

給定三角形位置,請問正方形的位置在哪裡。

Sample Input

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

Sample Output

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

測資參考圖

1
2
3
4
5
6
7
6 M P
5 P
4
3 X PM
2 P
1 M PM
1234567

Solution

範例輸出有錯,一開始以為自己寫錯。

由於盤面不大,可以直接進行全盤著色,分成四個象限去著色。當然如果盤面大一點,則必須考慮用外積等方式去完成象限判斷。由於是網格狀,邊界上會比較麻煩,方便起見還是直接著色。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
#include <stdio.h>
#include <string.h>
#include <assert.h>
const int MAXN = 128;
int g[MAXN][MAXN], ret[MAXN][MAXN];
const int dx[] = {-1, 0, 1, 0};
const int dy[] = {0, 1, 0, -1};
int main() {
int px, py, w, h;
int cases = 0;
while (scanf("%d %d %d %d", &px, &py, &w, &h) == 4) {
assert(w < MAXN && h < MAXN);
if (cases++) puts("");
memset(g, 0, sizeof(g));
memset(ret, 0, sizeof(ret));
for (int i = 0; i < MAXN; i++) {
int x, y;
x = px - i - 1, y = py + i + 1;
for (int j = 0; j < 2 * (i + 1); j++) {
if (x >= 0 && x <= w && y >= 0 && y <= h)
g[x][y] = 1;
y -= 1;
}
x = px - i, y = py + i + 1;
for (int j = 0; j < 2 * (i + 1); j++) {
if (x >= 0 && x <= w && y >= 0 && y <= h)
g[x][y] = 2;
x += 1;
}
x = px + i + 1, y = py + i;
for (int j = 0; j < 2 * (i + 1); j++) {
if (x >= 0 && x <= w && y >= 0 && y <= h)
g[x][y] = 3;
y -= 1;
}
x = px - i - 1, y = py - i - 1;
for (int j = 0; j < 2 * (i + 1); j++) {
if (x >= 0 && x <= w && y >= 0 && y <= h)
g[x][y] = 4;
x += 1;
}
}
for (int i = h; i >= 1; i--) {
int n, x;
scanf("%d", &n);
for (int j = 0; j < n; j++) {
scanf("%d", &x);
int tx, ty;
tx = x + dx[g[x][i] - 1];
ty = i + dy[g[x][i] - 1];
ret[tx][ty] = 1;
}
}
// for (int i = 1; i <= w; i++, puts(""))
// for (int j = 1; j <= h; j++)
// printf("%d", g[i][j]);
printf("%d %d %d %d\n", px, py, w, h);
for (int i = h; i >= 1; i--) {
int n = 0;
for (int j = 1; j <= w; j++)
n += ret[j][i];
printf("%d", n);
for (int j = 1; j <= w; j++)
if (ret[j][i])
printf(" %d", j);
puts("");
}
}
return 0;
}
/*
3 3 7 6
1 6
1 2
0
1 6
1 2
1 5
*/
Read More +

認知風格 設計篇

接續上一篇

調整方法

調適能力 (Adaptivity) 與調適性 (Adaptability)。前者能依據使用者的互動 (interaction) 自動修改應用程式的行為,後者則是依照預先定義好的選項來改變應用程式的行為。

網頁設計類型 優點 缺點
Adaptivity 迅速地貼近使用者,使用者不必做過多設定 由於自動變化,對於使用者教學會相當困難
Adaptability 客製化過程有參與感,可以更了解系統功能 入門困難,適應介面到可以客製的過程長

介面初步

1
2
3
4
5
6
7
8
9
10
test version
______ ______ ______ __ __
/\ == \ /\ __ \ /\ __ \ /\ \/ /
\ \ __< \ \ \/\ \ \ \ \/\ \ \ \ _"-.
\ \_____\ \ \_____\ \ \_____\ \ \_\ \_\
\/_____/ \/_____/ \/_____/ \/_/\/_/
+-------------------------------------+ +--------+
| type you want | |Find it!|
+-------------------------------------+ +--------+

從最基礎的設計中,明顯地會需要一個輸入框與觸發的按鈕。但是搜尋條件有幾種,從 作者 本文 標題 出版期刊 … 等,這些要怎麼設計才好?

《大神落伍了?這些搜索引擎比 Google 精確還更有趣》 這裡提到專門的搜尋引擎,可以針對音樂、圖片、社群、找人 … 等內容進行搜尋。他們是怎麼完成的?

介面二步

1
2
3
4
5
6
7
8
9
10
test version
______ ______ ______ __ __
/\ == \ /\ __ \ /\ __ \ /\ \/ /
\ \ __< \ \ \/\ \ \ \ \/\ \ \ \ _"-.
\ \_____\ \ \_____\ \ \_____\ \ \_\ \_\
\/_____/ \/_____/ \/_____/ \/_/\/_/
+-----------+ +--------+ +-------+ +--------+
| BOOK NAME | | AUTHOR | | WHERE | |Find it!|
+-----------+ +--------+ +-------+ +--------+

這裡的設計多了三個欄位,有些欄位可填、可不填,進行分開的搜尋。然而會不會發生有人誤填一些資訊,誤把人名當書名,造成搜尋結果不好。為了考量這些,基本上都是在搜尋結果中做一個排序,排序的優先權可能先按照書名、作者、地點。搜尋方式的比例差異通常呈現普遍性。

為了加速篩選結果,通常會先抓書名,如果書名不夠具有特色,則會嘗試抓作者名稱,如果作者出版很多本書,則會考慮出版時間、內容。能按照內容進行搜索的引擎,肯定規模很大。

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
______ ______ ______ __ __
/\ == \ /\ __ \ /\ __ \ /\ \/ /
\ \ __< \ \ \/\ \ \ \ \/\ \ \ \ _"-.
\ \_____\ \ \_____\ \ \_____\ \ \_\ \_\
\/_____/ \/_____/ \/_____/ \/_/\/_/
+-----------+ +--------+ +-------+ +--------+
| BOOK NAME | | AUTHOR | | WHERE | |Find it!|
+-----------+ +--------+ +-------+ +--------+
+---------------+---------------+---------------------+
| BOOK NAME | AUTHOR | WHERE |
+---------------+---------------+---------------------+
+---------------+
| TITLE |
+---------------+
| SUBTITLE |
+---------------+
| CHAPTER |
+---------------+
| NICKNAME |
+---------------+
| PROLOGUE |
+---------------+

有一種方式是提供搜尋條件的細節,來增加使用者的搜尋效率。上面就是一個範例,不僅僅只是官方書名提供搜索,細節劃分也是相當重要。甚至有人會將書給予暱稱,例如書的名稱太普通,卻以某種封裝、寫法出名,那麼這些書的名稱可能就比較特別。

對於場依賴的而言,看得到的搜尋介面相當重要,它們比較容易根據環境提供的條件,因此設計效能上影響很多。反過來看看場獨立的人,對於這個介面操作可能會將自己的想法投入,這些想法可能來自於自身或者是之前學習過的知識。

1
2
3
4
5
6
7
8
9
10
argument version
______ ______ ______ __ __
/\ == \ /\ __ \ /\ __ \ /\ \/ /
\ \ __< \ \ \/\ \ \ \ \/\ \ \ \ _"-.
\ \_____\ \ \_____\ \ \_____\ \ \_\ \_\
\/_____/ \/_____/ \/_____/ \/_/\/_/
+-------------------------------------+ +--------+
| learning in 23 days type:reference | |Find it!|
+-------------------------------------+ +--------+

對於隱藏式的操作想必也是相當上手。但這種使用需要教學,不是相當方便。可以由簡到繁地引領使用者,例如說第一次搜尋操作後,自動填充,提示使用者下一次搜尋的方法。

1
2
3
+-------------------------------------+ *--------*
| learning in 23 days | |Find it!| <--- click
+-------------------------------------+ *--------*

得到結果後

1
2
3
4
5
6
7
8
9
+-------------------------------------+ +--------+
| learning in 23 days type:reference | |Find it!|
+-------------------------------------+ +--------+
----------------------------------------------------------------------
Find result with ...
* learning in 23 days, author:Alice, type:reference
detail ...
* ...

現在的設計大多都是由 簡到繁 的方式,從蘋果軟體中都可以發現到這些特徵,將不常用的功能都先藏在特定的地方,先基礎地提供最低需求,當使用者發現結果越來越不好時,主動地去察覺要怎麼使用更複雜的操作。

介面三步

當然有些人搜尋的結果,會根據場依賴、場獨立來查閱,場依賴偏向是由觀眾評分、評論來判定是否找到正確的書,場獨立純文本內容為主。著重的觀點不同,為了成功銷售所有的書籍,顯示的方法如下兩種參考。

1
2
3
4
5
6
7
----------------------------------------------------------------------
Find result with ...
* Learning in 23 days, author:Alice, type:reference
detail ...
* WTF Learning, author:Blob
detail ..
1
2
3
4
5
6
7
8
----------------------------------------------------------------------
Find result with ...
* [HOT] STAR: 5, learning in 23 days, author:Alice, type:reference
detail ...
* STAR: 3, WTF Learning, author:Blob
detail ..
* ...

以上示範的是場獨立、依賴的兩種。當然上面的設計屬於階層式、也有水平擺放訊息的方式。其實還有很多種的!

1
2
3
4
* 1 TITLE AUTHOR
* 1.1 DETAIL
* 1.2 DETAIL
* 1.2.1 DETAIL
1
2
3
4
5
6
7
8
+-------+ +------------------------------------------+
| | | TITLE |
| | +------------------------------------------+
| COVER |
| | +------------------------------------------+
| | | AUTHOR, DETAIL |
| | | |
+-------+ +------------------------------------------+

如果找書不根據任何別人推坑的想法,階層式會是很好的細節來決策,嘗試把每一本書的概要都仔細讀過,階層式很吃 優先權順序 ,設計起來相當挑戰。

如果是下方的方式,比較偏向大致上已經在事前知道要買哪本書,有了大概目標。這樣的設計方式方便跳躍性的搜索,因為階層式會 改變高度 ,檢索時的位移量不同會造成操作時間會比較久,但是下方的那種方式屬於固定位移量。

至於對色彩學的配色部分,又分單色、雙色欄位,來加快視窗滑動的對齊。而每一個欄位的文字色彩也相當講究,用色數量越多,看起來越五花八門,面向的使用族群也越多。為了讓使用者有 貼切感 (服務的專一性),通常用色不大於三種。

回頭調整

Adaptivity 提供系統自動調整變化,最好提醒使用者可以切換回去舊的介面設計,否則多個使用者在現實中交談時,可能會發生矛盾的錯亂交流。

Adaptability 提供使用者自定義,這些功能從系統中抽離出來,對於開發者來說是件好事,讓所有功能盡可能地讓特定使用者使用,但普遍性來說,大多數的功能都會被忽略,甚至當作從來沒有過。

Adaptability 類型的網頁設計,提供高度穩定性、客製化,有利於長時間的網站經營,培養長遠的顧客為主,對於短期入門的新手而言,對於客製化的吃力程度不一,當尚未熟悉系統前,對於功能設定處於一知半解狀態,需要閱讀文檔或累積經驗。因此在使用前期效率並不高,有待一段時間後,使用效率就有機會突破 Adaptivity 類型的。

正因為使用時間的長短,Adaptability 比較適合高頻率使用的軟體,而且功能性很強。相較於 Adaptivity 給予功能性較低,但操作起來會比較耗時 (滑鼠移動、滾動) 的軟體。

Reference

Evaluation of a personalized digital library based on cognitive styles: Adaptivity vs. adaptability

課程需求才看得,雖然我對裡面的實驗方法一點也不認同,很多地方沒有考慮進去,實驗的順序性有考慮到,卻沒有去考慮適應面向的主體,拿幾次任務的時間來進行比較往往是不夠的,根據時間成長的效率比較也應該被提出。

課程公告

由於明天的課程有多數同學因畢業旅行而無法前來上課,因此明天上課的方式將不在課堂上進行,而改為同學閱讀所附的 paper,並找時間與組員討論所附的問題

請容許我罵一聲!我擦!為什麼不上課!雖然老師上課的認知風格與我不同,無法好好地學習知識,在盡可能地去適應,卻不斷地減少上課次數,學期結束前都還沒有適應吧。

Read More +

認知風格 找你所好

前言

曾想過明明有資源,卻怎麼樣都學不好嗎?在一個群體中學習,偏偏是落單的那一個人嗎?最近上課講到認知風格的相關內容,又對學習上有所障礙,到底是哪裡出了問題?

本文

如果一名學生的認知風格與教師類似,這個學生就會有更積極的學習經歷,學習將會改善。
隊員如果擁有 類似認知風格 ,將會使他們對於參與有更 積極 的感受。而認知風格的匹配將使參與者與人共事時感覺更為 舒適 ,雖然它並不能獨自保證取得勝利的結果。

明明身處同一個環境下,成長幅度卻來得比別人低,沒錯!學習上完全沒有共鳴,導致學起來相當痛苦,建造、聚集知識的方式不同,兩路不相逢,要如何引領另一個走向相同目標?

在思考知識、經驗傳播問題前,先來認識自己,當然以前人所提供的分類方法,既不是唯一也不能二值劃分,必然有人有於兩邊優缺點,呈現自然分布,很少人是相當極端的特例!

接下來要胡言亂語囉!不用擔心!只是學習的過程不同,不代表能力的侷限。

認知風格

Wholist-Analytic

看待一個事物時,採用策略的認知方法 (從舊有事物著手,了解處理事物之間的關聯)。來看看是怎麼 吸收 管理 新訊息。

Field Dependence & Independence

美國心理學家威特金 (Witkin) 在 1940 年代研究垂直知覺時首先發現的。強調認知的過程而非內容。

  • Field Dependence 場依賴
    偏向利用環境訊息來了解事物。
  • Field Independence 場獨立
    不依賴外界資訊,由個體的方式去理解。

場獨立自主、改造能力高,對於社會技能低。相反地,場依賴則在社會技能高,對於自主、改造能力低。當然從成長過程、性別差異,會逐漸地特化、轉變成另一類,並非完全由天生決定。

一種宅宅的既視感,不外乎差別就是願不願意聽外界,當個固執的聰明人還是頑固的蠢蛋,靠著外界成長就能成為現充,當個活在社會的人。場獨立則相當危險似的,若不是聰明的那一群,很容易被誤解、淘汰於社會中。 社會究竟是何物呢?

Leveller & Sharpener

赫爾茲曼 (K.Holzman) 和克萊因 (Klein, 1954) 使用 水平 - 尖銳 認知型來描述認知風格的另一維度。

  • Leveller 水平
    利用舊有資料產生關聯,並且加入新資料進去,但這樣做容易發生混淆,關聯的下場不保證能夠邏輯地去理解,相當於把多個物品放在同一處,「 他們應該是相似的 」的感覺,甚至有時還會因為擺滿物品而捨棄。一種以增加代替修改的 持久化算法 呢!
  • Sharpener 尖銳
    相對於水平,會想盡辦法 取代 掉舊有資料,把相似之處抓出來後,放大物品差異再放回去架子上,減少混淆的情況。但對於相似之處會被移除,聯想某個物品時,只會看到最後擺放的位置,回答問題只會去描述極端的差異?操作起來一定相當耗時,放大差異去分析處理,想必會很慢。

接下來分析行為上,有一種傾向。Leveller 運行 self-inwardness 模式,避免主動參與活動,對於指導、相助有迫切的需要,傾向於 自我貶抑 的性格。Sharpener 運行 self-outwardness 模式,相較於 Leveller 更喜歡 競爭 自我展現 ,對於成就有高度的需求,常常會逼迫自己推進。

看起來,Leveller 只是因為有 Sharpener 的人存在,才會偏向自我貶抑吧!而 Sharpener 擺明就是一個抖 M 推進器。

Impulsivity & Reflectivity

由卡根 (J.Kagan, 1964) 及同事提出來的,指在不確定條件下個體作出決定速度上的差異。

  • Impulsivity 衝動
    快速地成形自己的看法,對於問題可以很快速地回答,有迫切做出決定的慾望。
  • Reflectivity 反思
    相反地,不斷地反思自己看法,考量所有可能性才能回答問題,並且設想所有評估所有答案的優缺。

這兩種在整體性的工作上沒有任何差異,但反思型學習速度比較慢。

Diverging & Converging

由吉爾福特 (Guilford, 1967) 在介紹智力模型時提出的。

  • Diverging 發散
    回答問題時,答案允許多個,接受多種解決方案,強調多樣性、創造性。
  • Converging 收斂
    強調找到一種明確的正確答案。

只有一種答案不是很無趣嗎?對於我而言,越是要求那麼唯一,就越不是答案,也許是因為找不到唯一的答案吧,才去接受多種選項的可能性。

Holist & Serialist

由帕斯克 (G.Pask, 1972) 和司科特 (Scott) 提出的。

  • Holist 整體
    大規模搜索資料,進行較大的特徵假設,嘗試去找到一個模式、關係來理解。
  • Serialist 序列
    單一方向、單一步驟來完成理解,對於細節較為著重,不在意整體的連接關係。

序列導向的人肯定對於讀書這類的序列資料很感興趣,也比較能一頁一頁的讀完,對於整體導向的人而言,常常會跳躍章節去讀,才會逐漸深入理解。

序列導向通常面臨的是細節著重,而無法看到整體概念,整體導向則由整體概念來理解,而缺少細節的驗證。從具有遠見的程度來看,整體導向會比序列導向來得高,正因為不清楚,才有廣闊的視野吧!

Verbaliser-Imager

心理學家 Paivio (1974) 提出了長時記憶信息如何被加工存儲的理論。影像儲存還是文字儲存?有一種原始影像資料和壓縮過後的大綱儲存差別,語言系統一改,壓縮而成的文字內容會造成理解錯誤吧?

Verbaliser & Imager

Riding 和 Taylor 最初提出的。

  • Imager 形象
    傾向於以視覺表象的形式思維的人被稱為形象型的人。
  • Verbaliser 言語
    傾向於以詞的形式思維的人被稱為言語型的人。

根據許多研究,當學習材料不匹配時,得到的成績往往不理想。而在人格性質上,往往言語型偏向外向、形象型偏向內向。等等,好像明白為什麼語言類型的科目會比較難學,因為很難形象化,導致只有言語型的人可以在這一方面學習得不錯。

Reference

Read More +

虛擬實境 論文研讀 Part 2

接續上一篇,接下來會有很多數學,自己也是一知無解狀態。

QEM

QEM 方程如下,當找到許多在等值曲面上的 $p_k$ 時,接著要找到一個最小 $v$ 參數為滿足目標方程

$v^{M} \leftarrow \text{arg } \underset{v}{min} \sum_{k} (n^t_{k} (v - p_{k}))^2$

為了找到這個 $v$

$v^M \leftarrow v^M + Q^+ (q - Q v^M)$

其中矩陣$Q = \sum_{k} n_{k} n^{t}_{k}$

使用 SVD ( singular value decomposition ),奇異值分解去找解,這個計算好像在巨量資料中也存在 SVD,這世界太可怕了! )。 在找到 $v^M$ 之後,進行簡化模型的縮點 (減少節點數)。經由這一步,可以更貼近物體的輪廓線,來強化邊緣的存在。

為了減少退化三角形跟三角網格的品質,可以藉由 Delaunay Tetrahedralization 再刷新一次。修改、刪除的點其實並不多於全局 (大多都是邊緣?),那麼可以利用鬆弛的方式進行更新會比全局刷新還來得快速。

實戰

接下來這篇論文的作者,要進行實戰比較 (一起來吸收別人怎麼去驗證、統計、比較!實驗方法也是相當重要的。),查看每一次迭代的是否可以的過程、速度。

誤差評定

利用模擬的模型 (幾何模型構成,拿真物要找到物體表面的方向量有點麻煩),來看梯度方向的準確性,簡單來說就是拿實際結果與實驗結果中的一個點,於表面上的法向量夾角作為誤差大小。

$\varepsilon_{n}(p) = \text{arccos } (n(p)^t m(p))$

梯度估計

即使是誤差,平滑也是好事。梯度越小越平滑。

接著它拿兩個物體 sphere 和 fandisk mesh (球體和一個 ???) 進行測試,看來在那些特殊的地方的誤差。算法可能會在某些特殊邊緣、平面的運行效果不好 (算法擅長於那些特徵計算、不善長什麼,來釐清這一點)。

視覺化時,利用下列二種方式凸顯梯度 central differences、Sobel operator (索貝爾運算就是在影像處理運用中,檢測邊緣、圖像梯度用的)。論文作者接下來提供一的新穎 CT value 的計算公式,與一般傳統的不同,讓這個誤差梯度下降。經由上面兩種方式的梯度表示,發現論文作者提供的這種會再更好一點點。

討論

  • 提供一個更準確的 CT value 的計算方式
  • 動態運作 ODT, QEM 的方式

比較 Grid-Based

拿其他的建造方案,如 Grid-Based Polygonization,進行邊緣銳利比較。使用 Hausdorff distance 比較好壞,當然地 Grid-Based 沒對齊好,基本上贏不過三角化的版本!Grid-Based 取樣點夠密集才能勝過 vary triangle 的建模吧!在大多共平面的狀況下,三角形建模的方式需要頂點數非常少。

時間消耗

由於精準度上的調校,提出的方法比起常規的建模是消耗更多時間,但 CT 掃描數據的時間通常需要 10min 到 1hr,但是提出的方法比掃描時間還快。

也就是說讀取資料很慢,處理速度不用這麼快也沒關係,算起來有種 $O(n)$ 的效能。

空間消耗

由於過程中需要大量的矩陣、附加值於記憶體中,會消耗上 GB 的使用空間來維護操作。但現代電腦的記憶體可以超過 10GB 以上,因此這個缺點可以忽略不計。

實作細節

為了加速運行,特別將物體表面周圍的取樣多、密集一點,對於其他地方的取樣少點,如此一來計算量會少很多。

插值

$Tiso= (f(gi)+f(gj))/2$ 中提到插值法,但插值法有很多種,如 tricubic interpolation、bicubic interpolation … 等,如果選擇別種的插植法,效果會不會比較好呢?經過它們的梯度估算方法,他們所提到的插值法會稍微好一些。

結論

提出一個可以用 CT 圖去建模的方法,仰賴的不是三維空間 volumetric data,並且可以提供一個更高精度的建模方法,用少許的三角形就能構造出相當銳利的結果。

  1. 以上都是建立在單一素材上,如果由複合的素材構成,可能是一個無法解決的問題。
  2. 對於光束敏感的物體,CT 的建造方法可以支持更好。
  3. 如果一開始的四面體不夠完善,有可能無法捕抓到邊緣。自適應的 meshing 也許能解決這問題。

提出一個速度慢、空間多的算法,但是現代電腦記憶體夠,速度慢沒關係,比輸入還快就行了

Read More +

近代加密 快速冪次計算

先來上競賽中最常見到的快速冪次寫法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
long long mul(long long a, long long b, long long mod) {
long long ret = 0;
for( ; b != 0; b>>=1, (a<<=1)%=mod)
if(b&1) (ret += a) %= mod;
return ret;
}
long long mpow(long long x, long long y, long long mod) {
long long ret = 1;
while (y) {
if (y&1)
ret = mul(ret, x, mod);
y >>= 1, x = mul(x, x, mod);
}
return ret;
}

這樣的寫法,只運算 64-bits 內的結果相當不錯,與接下來講的算法速度差異並不大。但對於近代加密所需要的離散對數過程,則會有放大速度差異,因為 x, y, mod 三個參數的 bit-length 都相當長。

Right-To-Left

做法就是最普遍見到的。

1
2
3
4
5
6
7
8
9
void pow(x, a) {
R = 1, g = x
for i = 0 to n, step 1
if (a[i] == 1) // a[i] mean i-bit in a
R = R * g
g = g * g
return R
}
1
2
3
4
5
6
example: a = 26 = (11010)
---
#iterator 0 1 2 3 4
#g x, x^2, x^4, x^8, x^16, ...
binary 0 1 0 1 1
x^a = x^2 x^8 x^16 = x^26

Left-To-Right

1
2
3
4
5
6
7
8
void pow(x, a) {
R = 1
for i = n-1 to 0, step -1
R = R ^ 2
if (a[i] == 1)
R = R * x
return R;
}
1
2
3
4
5
example: a = 26 = (11010)
---
#iterator 0 1 2 3 4
binary 1 1 0 1 0
R x^1 x^11 x^110 x^1101 x^11010

也就是說,每一次迭代,將會將指數左移 (R = R ^ 2 就是要移動 R^xxxx 變成 R^xxxx0,切著看是否要乘上 x 來變成 R^xxxx1),這方法雖然不錯,但是計算高位元空迴圈可是不能忽略!當然對於設計電路的人,固定變量的迴圈展開後就不存在這問題。

效能上,期望的乘法次數與 Right-To-Left 是差不多的!長度為 n bits 的數字,期望值會有 $n/2$ 個 1,因此大約需要 $n/2$ 次乘法。

Left-To-Right(2-bits)

1
2
3
4
5
6
7
8
9
10
void pow(x, a) {
pre-computation: x^2, x^3
R = 1
for i = n/2 to 0, step -2
R = R ^ 4
if (a[i+1]a[i] != 0)
R = R * x^(a[i+1]a[i])
return R
}
1
2
3
4
5
6
example: a = 1737 = (01 10 11 00 10 01)
---
#iterator 0 1 2 3 4 5
R x^1 x^4 x^24 x^108 x^432 x^1736
x^6 x^27 x^434 x^1737
a[i+1]a[i] 01 10 11 00 10 01

長度為 n bits 的數字,倆倆一起期望值會有 $3n/8$ 個非 0 (00, 01, 10, 11 中 4 個有 3 個 $!= 0$,計算組數只有 $n/2$),因此大約需要 $3n/8$ 次乘法。比剛剛快上一點呢 ( $3n/8 < n/2$ )!

Left-To-Right(sliding)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
void pow(x, a) {
pre-computation: x^3
R = 1
for i = n-1 to 0
if a[i]a[i-1] = (11)
R = R ^ 4
R = R * x^3
i -= 2
else if a[i]a[i-1] = (00)
R = R ^ 4
i -= 2
else if a[i] = (0)
R = R ^ 2
i -= 1
else if a[i] = (1)
R = R ^ 2
R = R * x
i -= 1
return R
}
1
2
3
4
5
example: a = (1 10 11 00 10 01)
---
#iterator 0 1 2 3 4 5 6
R x^11 x^11011 x^11011001 x^11011001001
x^110 x^1101100 x^1101100100

期望的乘法次數 $n/3$,這很可怕,別問我!

Summary

Algorithm Name Table Size #squaring Average #Multiplication
Right-To-Left 1: $x^{2^i}$ $n$ $n/2$
Left-To-Right 1: $x$ $n$ $n/2$
Left-To-Right(2-bits) 3: $x$, $x^2$, $x^3$ $n$ $3n/8$
Left-To-Right(sliding) 2: $x$, $x^3$ $n$ $n/3$

Attack

上述四種方法中,不外乎都存在 Conditional Jump 出現,會導致執行速度跟程式計數器 (Program Counter) 移動上會有所差別。可以進行實作攻擊,從分析時間、電源不穩攻擊,來找到 $a$ 究竟是何物 (通常要偷的都是 $x^a$ 上面的 $a$)!電源雜訊干擾於當指令執行到 Conditional Jump 時,干擾 $R$ 的計算發生錯誤,如果沒有發生錯誤,表示 $a \left [ i \right ] = 0$,反之就能知道 $a \left [ i \right ] = 1$

很趣味地是提供一個不需要 Conditional Jump 的寫法 (實作上),一樣會有這種問題!

error example

1
2
3
4
5
R[1] = 1
for i = n-1 to 0, step -1
R[1] = R[1] ^ 2
R[a[i]] = R[1] × g
return R[1]

提供一個不相干的垃圾暫存器 (redundancy register) 來減少 Jump 的問題,很明顯地 電源雜訊干擾 的攻擊仍然存在!這作法很有趣的啊!只是會被攻擊。

Montgomery Ladder

1
2
3
4
5
R[0] = 1, R[1] = g
for i = n-1 to 0, step -1
R[1-a[i]] = R[0] × R[1]
R[a[i] = R[a[i]] × R[a[i]]
return R[0]

這作法避開之前的問題,解決其中一種被攻擊的方案。

過程中都滿足 $R \left [ 0 \right ] = R \left [ 1 \right ] \times g$,同時 $R \left [ 0 \right ]$ 會是最後的答案。

1
2
3
4
5
6
example: a = 26 = (11010)
---
#iterator 0 1 2 3 4 5
binary 0 1 1 0 1 0
R[0] 1 x x^3 x^6 x^13 x^26
R[1] x x^2 x^4 x^7 x^14

明顯地,$R \left [ 1 \right ]$ 的計算是為了下一次為 $a \left [ i+1 \right ] = 1$ 而存在的!

Read More +

資訊安全 期中考筆記

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

Problem

  1. Please compare the CFB mode the OFB mode by providing a table indicating at least 5 of their advantage and corresponding disadvantage.

  2. This question is about issues of random access of encryption and decryption. Define notations or symbols you need before providing your answer. Please describe how to use CFB mode and CTR mode to randomly encrypt and decrypt a file Why OFB mode is inappropriate to randomly encrypt and decrypt a file?

  3. What is the one-time pad (please provide a figure to explain it) and what is the main security requirement ?

  4. Follow

    1. What is the main weakness (disadvantage) of ECB mode ?
    2. How CBC resolves this security problem (explained in mathematical approach) ?
  5. Follow

    1. Block ciphers process messages in blocks like an extremely large substitution, but this is very difficult to implement for a very big block size. What is Shannon’s idea to realize a block cipher with big block size ?
    2. Feistel cipher structure provides a real implementation of Shannon’s idea. Please explain the main features of Feistel cipher structure.
  6. What is the avalanche effect of a block cipher ?

  7. What is the Triple -DES with two keys ? Please explain the process the derive the complexity of meet-in-the-middle attack on Triple-DES with two keys.

  8. For all AES round operations, which operation(s) provides substitution and which operations(s) provides diffusion ?

  9. Please describe the details of BBS pseudorandom number generator.

  10. Below is the pseduo code of RC4. Please design a modified RC4 version with message dependent property of key stream.

1
2
3
4
5
6
7
8
9
init(master_key) // shuffle S-array with master_key
i = j = 0
for each message byte M
i = (i + 1) mod 256
j = (j + S[i]) mod 256
swap(S[i], S[j])
t = (S[i] + S[j]) mod 256
C = M xor S[t]

Notes

Part 1

請參照 《資訊安全 小考筆記》

Part 2

請參照 《資訊安全 小考筆記》

Part 3

one-time pad 概念圖如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
+------------+ +------------+
| random key | | random key |
| generator | | generator |
+------------+ +------------+
| |
| |
| Ki | Ki
| |
| |
Pi +--v--+ Ci +--v--+ Pi
------------------>+ XOR +---------------------->+ XOR +---------------->
+-----+ +-----+
From Alice To Blob

one-time pad 主要是靠 key 不重複使用來維護資料安全,但是雙方同時產生出相同的 random key 是很困難。由於不重複使用,靠一個 random key 的產生提供 random mask 消除明文和祕聞之間的關係。但是雙方都要相同 random key 是相當困難的,通常 generator 是由某個偽隨機數產生器,由演算法產生的到底是不是隨機數呢?

一定要 random key,不能有任何的 dependent、 不能重複 (reuse) ,否則會有 已知明文攻擊

Part 4

ECB mode 在相同明文對應相同的密文,這容易受到大多數的攻擊,如收集已知明文或密文所進行的攻擊。由於明文和密文的關係明確,也因此在 圖片資料 加密傳送,會出現 大量重複 的密文。

CBC mode 提供 random mask 的效果

$$C_{-1} = IV \\ C_{i} = E_{k}(C_{i-1} \text{ XOR } P_{i}) \\$$

$C_{i-1} \text{ XOR } P_{i}$ 操作中可以發現到,讓相同的明文對應到不同的密文 (在前一個密文不同時),解決 ECB 在相同明文加密只會對應種密文。

Part 5

Shannon’s idea 請參照 《資訊安全 小考筆記》

Feistel cipher structure 藉由三個規則實作

  • 多個回合 (round)
  • 藉由一半的訊息加密另一半的訊息
  • 兩個半段訊息位置交換

Part 6

Avalanche effect 讓相似的明文 Pi, Pj (漢明距離 $H(Pi, Pj) = 1$ … 之類的) 對應的密文 Ci, Cj 能有相當大的差異 (漢明距離 $H(Ci, Cj) = \text{half-bit}$ 的期望值),將只有一點不同的差異,擴散到密文的每個地方,放大差異的效果。

Part 7

Triple-DES

$E_{k_{1}}\left [ D_{k_{2}} \left [ E_{k_{1}} \left [ P_{i} \right ] \right ] \right ] = C_{i}$

當指使用兩次的 DES,受到中間相遇法的攻擊,理論上的複雜度與 1 個 DES 一樣,複雜度大約都在 $O(2^{56})$,因此效果並沒有變得更好。然而在 Triple-DES 複雜度就提升到 $O(2^{112})$

中間相遇法攻擊

$$X = D_{k_{2}} \left [ E_{k_{1}} \left [ P_{i} \right ] \right ] \\ C_{i} = E_{k_{1}} \left [ Y \right ]$$

從已知的明文、密文,目標是讓 $X = Y$,窮舉 key,如果發生 $X = Y$ 則表示找到一組可行的解。窮舉得到 $X$ 的複雜度為 $O(2^{56} \times 2^{56}) = O(2^{112})$,而 $Y$ 則需要 $O(2^{56})$,當 $X = Y$ 符合時,則表示 key 找到!

當然也可以抽換相遇的地點,如下是另外一種

$$X = E_{k_{1}} \left [ P_{i} \right ] \\ C_{i} = E_{k_{1}} \left [ D_{k_{2}} \left [ Y \right ] \right ]$$

不管怎麼切割,都至少要窮舉兩把 key 來得到其中一個相遇結果,複雜度都落在 $O(2^{112})$。如果說對應兩個表的結果需要 $O(log \text{ n})$ 的時間,則複雜度為 $O(112 \times 2^{112})$

Part 8

Step Function
Substition Bytes substitution
Shift Rows diffusion
Mix Column diffusion, substitution
Add Round Key substitution

比較討厭的是 Shift Rows 為什麼提供是 diffusion,明明對於兩個相似明文加密上,漢明距離沒有增加!但是 diffusion 不只是讓差異數量增加,還要考量差異位置的變動,相鄰差異的位置被轉移到兩個遠處,就能提供 diffusion!這會讓攻擊者無從判定差異的對應。

Part 9

Blum Blum Shub

$$X_{i} = X_{i-1}^2 \text{mod } N \\ N = p \times q \\ p, q \text{ is prime nubmer, } \\ p \equiv 3 (\text{mod } 4) \\ q \equiv 3 (\text{mod } 4)$$

Part 10

message dependent RC4

第一版本

1
2
3
4
5
6
7
8
9
10
11
i = j = 0, Cprev = 0
for each message byte M
i = (i + 1) mod 256
j = (j + S[i]) mod 256
swap(S[i], S[j])
t = (S[i] + S[j] + Cprev) mod 256
C = M xor S[t]
if sender
Cprev = C
if receiver
Cprev = M

好像不管怎麼寫兩邊似乎都很難相同。

第二版本

1
2
3
4
5
6
7
i = j = 0, C = 0
for each message byte M
i = (i + C) mod 256
j = (j + S[i]) mod 256
swap(S[i], S[j])
t = (S[i] + S[j]) mod 256
C = M xor S[t]

感覺上述的寫法,萬一 C 被竄改,會導致錯誤擴散,而且是全部失效,這讓我非常納悶。

Read More +

[Tmt514] Beverage Cup 2 F - No challenge No life

Problem

這次要挑戰的題目為 UVa 12647 - Balloon,由於網路上有一些線段樹的作法,如

http://www.cnblogs.com/yours1103/p/3426212.html

這裡可以看到有一些人利用線段樹來完成操作,實際上的做法根據掃描線,y 座標從高度低到高、或者是高到低的線段依序放入,到時候查找第一個獲最後一個覆蓋的線段編號。

UVa 12647 - Balloon 主要是在問,給予一些天花板,請問放置一個氣球,氣球會沿著天花板歪斜的地方移動 (如果沒歪則停止),詢問最後停留的位置。有可能會飛到無窮遠,則輸出最後的 x 座標,反之輸出停留的 (x, y) 地點。如果發生一個地點有數個天花板,發生擦邊情況,則氣球會依據第一個碰觸到的天花板進行移動。

Solution

既然發現它們 (搜尋可以找到至少兩個大大的線段樹寫法) 基本上都是按照覆蓋的方式,找到覆蓋編號去完成移動、建造飛行的所有可能。

明顯地,由於天花板線段的斜率有正有負,不能保證根據 y 座標排序可以決定飛行到下一個的高度的 x 範圍。因此交錯 y 座標線段,讓覆蓋方案失效。

下面有數組測資,可以讓史蒂芙掉測資、也會讓網址中的代碼掉。史蒂芙認為覆蓋不好,那用最大最小值去維護如何!這樣也許就好點了吧?但不幸地,這麼做一樣會遇到排序 y 的問題,不管怎麼排序,都會造成建構方法不正確。

challenge 的題目還不太會傳,中間有點小問題。

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
#include <stdio.h>
int main() {
// puts("4 1");
// puts("3 1 12 4");
// puts("5 2 2 3");
// puts("1 4 4 3");
// puts("7 5 14 5");
// puts("4");
puts("3 3");
puts("5 5 10 4");
puts("2 2 6 4");
puts("0 3 4 4");
puts("0");
puts("1");
puts("2");
// puts("7 4");
// puts("3 1 12 4");
// puts("5 2 2 3");
// puts("1 4 4 3");
// puts("7 5 14 5");
// puts("25 5 30 4");
// puts("22 2 26 4");
// puts("20 3 24 4");
// puts("4");
// puts("20");
// puts("21");
// puts("22");
return 0;
}
/*
3 3
5 5 10 4
2 2 6 4
0 3 4 4
0
1
2
4 1
3 1 12 4
5 2 2 3
1 4 4 4
7 5 14 5
4
4 1
3 1 12 4
5 2 2 3
1 4 4 3
7 5 14 5
4
*/
Read More +

[Tmt514] Beverage Cup 2 G - Strange Permutations

Problem

排列 1 到 n 的整數,使得相鄰兩數互質,部分位置一定要填入某些數字,請問剩餘數字的填法方案數為何?

Sample Input

1
2
3
1
8
1 2 0 0 0 0 7 8

Sample Output

1
2

Solution

bitmask 下去 dp[N][1<<N][N]dp[i][j][k] 表示前 i 個位置,已經使用的數字狀態 j,最後一個數字 k。不曉得需不需要滾動,記憶體用量驚人。萬萬沒想到,撰寫過程中坑了一個建表的 gcd[20][20],當 n = 20 時,直接 index out of bound,先撇開這個蠢錯。滾動數組下去玩,真是誘人的時間限制,再次得到 9.990s TLE 的事蹟 !仔細想想,運算過程中只出現加法,將 mod 運算替換成條件判斷 + 減法,

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
#include <stdio.h>
#include <string.h>
#include <algorithm>
using namespace std;
const int MOD = 1000000007;
const int MAXN = 20;
int dp[2][1<<MAXN][MAXN], used[2][1<<MAXN][MAXN], ucases = 0;
int g[MAXN+20][MAXN+20];
int vaild(int i, int a, int b) { // a contract b
if (i == 0) return 1;
return g[a+1][b+1];
}
int main() {
for (int i = 1; i <= MAXN; i++) {
for (int j = 1; j <= MAXN; j++)
g[i][j] = __gcd(i, j) == 1;
}
int testcase, n;
int A[MAXN];
scanf("%d", &testcase);
while (testcase--) {
scanf("%d", &n);
int cant = 0;
for (int i = 0; i < n; i++) {
scanf("%d", &A[i]);
if (A[i])
cant |= 1<<(A[i]-1);
}
int flag = 0;
int mask = (1<<n)-1;
int ret = 0;
// memset(dp, 0, sizeof(dp));
ucases++;
used[flag][0][0] = ucases;
dp[flag][0][0] = 1;
for (int i = 0; i < n; i++) {
int p = flag, q = !flag;
flag = 1 - flag;
ucases++;
// for (int j = mask; j >= 0; j--)
// for (int k = 0; k < n; k++)
// dp[q][j][k] = 0;
for (int j = (1<<n)-1; j >= 0; j--) {
for (int k = 0; k < n; k++) {
if (used[p][j][k] != ucases-1)
continue;
int ways = dp[p][j][k];
if (ways == 0)
continue;
if (A[i] == 0) {
for (int p = 0; p < n; p++) {
if ((j>>p)&1)
continue;
if (!vaild(i, k, p))
continue;
if (used[q][j|(1<<p)][p] != ucases)
used[q][j|(1<<p)][p] = ucases, dp[q][j|(1<<p)][p] = 0;
dp[q][j|(1<<p)][p] += ways;
if (dp[q][j|(1<<p)][p] >= MOD)
dp[q][j|(1<<p)][p] -= MOD;
}
} else {
for (int p = A[i]-1; p <= A[i]-1; p++) {
if ((j>>p)&1)
continue;
if (!vaild(i, k, p))
continue;
if (used[q][j|(1<<p)][p] != ucases)
used[q][j|(1<<p)][p] = ucases, dp[q][j|(1<<p)][p] = 0;
dp[q][j|(1<<p)][p] += ways;
if (dp[q][j|(1<<p)][p] >= MOD)
dp[q][j|(1<<p)][p] -= MOD;
}
}
}
}
}
for (int i = 0; i < n; i++) {
if (used[flag][(1<<n)-1][i] != ucases)
continue;
ret += dp[flag][(1<<n)-1][i];
ret %= MOD;
}
printf("%d\n", ret);
}
return 0;
}
/*
10
20
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
4
0 2 4 0
8
1 3 5 0 0 0 0 0
8
1 2 0 0 0 0 7 8
8
1 0 5 8 0 0 0 0
8
0 2 0 7 0 0 0 8
8
0 2 0 0 0 0 0 8
8
0 2 0 0 0 0 0 0
8
0 0 0 0 0 0 0 0
7
0 0 0 0 0 0 0
1
0
*/
Read More +

[Tmt514] Beverage Cup 2 E - Kirino in Google Code Jam

Problem

2015 Google Code Jam Round 1A 中,C - Logging 有點嚴重的問題!很多精準度不高的代碼都通過了!現在要求去找到測資 tricky cases,讓他們通通 WA!

原題是對於任意平面點,找到要移除最小的平面點,使得自己在凸包邊緣上。標準的極角排序,維護 180 度以內 (半平面) 的點個數。

atan2 esp = 1e-12 not enough for $x, y \in \left [0, 10^6 \right]$,極角排序替代方案!快來戳我的講法,看 POJ 類似的題目,精準度要壓到 1e-16 呢,所以誤差還要抓更小才行。

Solution

等等,challenge 這一題時,自己的代碼也 WA 了!

一開始誤以為是 index out of bound,assert() 下去都沒發生。後來估計是 atan2(y, x) 的誤差,對此誤差早有耳聞,解題撞壁的機會很低。咱很蠢,隨機生了 1000 萬個平面點,按照 atan2 排序,檢查相鄰兩個座標的外積 (叉積) 是否為逆時針,跑一次隨機生成大概能爆出一個誤差的相鄰點,十來次得到 n 個點,隨機組合這 n 個點於四個象限,再亂撒少數個點,再跟自己撰寫的代碼做比對 (中間又測了上萬組)。

atan2 誤差測試

atan2_eps_test
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
#include <stdio.h>
#include <stdio.h>
#include <math.h>
#include <vector>
#include <assert.h>
#include <time.h>
#include <algorithm>
using namespace std;
#define eps 1e-8
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 {
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);
}
};
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;
}
bool operator!=(const Seg &other) const {
return !((s == other.s && e == other.e) || (e == other.s && s == other.e));
}
};
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());
}
double distSeg2Point(Pt a, Pt b, Pt p) {
Pt c, vab;
vab = a - b;
if (between(a, b, p)) {
c = getIntersect(Seg(a, b), Seg(p, Pt(p.x+vab.y, p.y-vab.x)));
return (p - c).length();
}
return min((p - a).length(), (p - b).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 monotone(int n, Pt p[], Pt ch[]) {
sort(p, p+n);
int i, m = 0, t;
for(i = 0; i < n; i++) {
while(m >= 2 && cross(ch[m-2], ch[m-1], p[i]) <= 0)
m--;
ch[m++] = p[i];
}
for(i = n-1, t = m+1; i >= 0; i--) {
while(m >= t && cross(ch[m-2], ch[m-1], p[i]) <= 0)
m--;
ch[m++] = p[i];
}
return m-1;
}
const double pi = acos(-1);
int main() {
srand ( time(NULL));
vector< pair<double, Pt> > V;
for (int i = 0; i < 10000000; i++) {
int X, Y;
X = (rand()*rand())%2000000 - 1000000;
Y = (rand()*rand())%2000000 - 1000000;
V.push_back(make_pair(atan2(Y, X), Pt(X, Y)));
}
sort(V.begin(), V.end());
for (int i = 0; i+1 < V.size(); i++) {
if (cross2(V[i].second, V[i+1].second) != 0 && fabs(V[i].first - V[i+1].first) < 1e-12)
printf("%.0lf %.0lf %.0lf %.0lf\n", V[i].second.x, V[i].second.y, V[i+1].second.x, V[i+1].second.y);
}
// long long a = 599430;
// long long b = 1428722;
// long long c = 648824;
// long long d = 1546451;
// long long a = 885828;
// long long b = 737167;
// long long c = 898961;
// long long d = 748096;
//
// printf("%lld\n", a * d - b * c);
// printf("%lf %lf, %d\n", atan2(b, a), atan2(d, c), fabs(atan2(b, a) - atan2(d, c)) < 1e-12);
return 0;
}
/*
885828 737167 898961 748096
854425 732894 706721 606199
-971645 988877 -838743 853618
993753 -652232 991850 -650983
620105 659001 916399 973880
*/

誤差數據產生

testdata
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
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <time.h>
#include <set>
#include <algorithm>
using namespace std;
double random() {
int r = rand();
return (double) r / RAND_MAX;
}
int vvv[20] = {885828, 737167, 898961, 748096,
854425, 732894, 706721, 606199,
-971645, 988877, -838743, 853618,
993753, -652232, 991850, -650983,
620105, 659001, 916399, 973880};
main() {
int n, m, t, a, b, c, tmp;
int z, i, j, k, l, p, q;
srand ( time(NULL));
freopen("in.txt", "w+t", stdout);
int T = 10000;
printf("%d\n", T);
while(T--) {
int n = 30, m = rand()%n + 1;
printf("%d\n", n);
set< pair<int, int> > S;
S.insert(make_pair(0, 0));
printf("%d %d\n", 0, 0);
for (int i = 1; i < n; i++) {
int x, y;
do {
if (rand()%10 < 8) {
x = vvv[rand()%20], y = vvv[rand()%20];
if (rand()%2) x = -x;
if (rand()%2) y = -y;
} else {
x = rand()%100 - 50, y = rand()%100 - 50;
}
} while (S.count(make_pair(x, y)));
S.insert(make_pair(x, y));
printf("%d %d\n", x, y);
}
}
return 0;
}

對照組

利用外積的計算方案,誤差情況會更小。又因為是整數座標,基本上是不產誤差。

fixed-bug-Problem-C-Logging
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
#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;
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;
}
Pt D[4096];
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);
}
int main() {
int N, testcase, cases = 0;
double x, y;
scanf("%d", &testcase);
while (testcase--) {
scanf("%d", &N);
for (int i = 0; i < N; i++) {
scanf("%lf %lf", &x, &y);
D[i] = Pt(x, y);
}
printf("Case #%d:\n", ++cases);
if (N == 1) {
puts("0");
continue;
}
for (int i = 0; i < N; i++) {
vector< Pt > A;
for (int j = 0; j < N; j++) {
if (i == j)
continue;
Pt p = D[j] - D[i];
A.push_back(p);
}
sort(A.begin(), A.end(), cmp);
int M = (int)A.size();
int l = 0, r = 0, cnt = 1;
int ret = 0;
for (l = 0; l < M; l++) {
if (l == r)
r = (r+1)%M, cnt++;
while (l != r && cross2(A[l], A[r]) >= 0) {
r = (r+1)%M, cnt++;
}
ret = max(ret, cnt);
cnt--;
}
printf("%d\n", N - ret);
}
}
return 0;
}

Final

生了 10000 組,終於在裡面發現其中幾組。

final
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#include <stdio.h>
int main() {
puts("1\n30\n0 0\n-971645 838743\n748096 -988877\n-652232 -993753\n737167 -838743\n-48 27\n706721 -885828\n606199 854425\n659001 -993753\n898961 885828\n-659001 885828\n748096 -973880\n21 -13\n-748096 606199\n-732894 991850\n13 -12\n659001 -737167\n-32 -32\n737167 -748096\n650983 -971645\n650983 -732894\n854425 -606199\n-606199 885828\n916399 -988877\n652232 -838743\n-606199 988877\n-620105 -652232\n-748096 -737167\n24 -23\n916399 854425\n");
return 0;
}
/*
1
30
0 0
-971645 838743
748096 -988877
-652232 -993753
737167 -838743
-48 27
706721 -885828
606199 854425
659001 -993753
898961 885828
-659001 885828
748096 -973880
21 -13
-748096 606199
-732894 991850
13 -12
659001 -737167
-32 -32
737167 -748096
650983 -971645
650983 -732894
854425 -606199
-606199 885828
916399 -988877
652232 -838743
-606199 988877
-620105 -652232
-748096 -737167
24 -23
916399 854425
*/
Read More +