近代加密 數位簽章 DSA 筆記

DSA

Digital Signature Standard (DSS) 數位憑證標準,而有一個 DSA 演算法來完成憑證。

每一次憑證傳送三個參數 $(r, s, M)$,利用私用金鑰進行加密。讓對方使用公鑰進行驗證。

  • $r = (g^k \mod p) \mod q$
  • $s = k^{-1}(SHA(M) + x \times r) \mod q$

give receiver $(r, s, M)$, we have random (one-time) $k$ & private key $x$

每一次簽名時,最好使用隨機亂數 $k$ 作為簽章用途,隨用即棄,若發生重複使用,將會發生被攻擊,被攻擊者拿到私鑰後,攻擊者就能進行偽裝。

Attack

if reused $ k $

如果重複使用隨機變數 $k$,可以藉由夾擊的方式得到,雖然不知道加密的隨機亂數 $k$ 究竟是什麼,藉由夾擊可以把 $k$ 消除,利用乘法反元素反求 $x$ 是多少,公式如下所述。

  • $ k = s1^{-1} (SHA(M1) + x \times r) \mod q $ - (1)
  • $ k = s2^{-1} (SHA(M2) + x \times r) \mod q $ - (2)

subtract (1) - (2)

  • $ 0 = s1^{-1} SHA(M1) - s2^{-1} SHA(M2) + (s1^{-1} - s2^{-1}) x \times r \mod q $
  • $ s1^{-1} SHA(M1) - s2^{-1} SHA(M2) = (s2^{-1} - s1^{-1}) x \times r \mod q $
  • $ x = (s1^{-1} SHA(M1) - s2^{-1} SHA(M2)) \times (s2^{-1} - s1^{-1})^{-1} \times r^{-1} \mod q $

get private key. Dangerous !

Verify

驗證的正確性與否,接收者需要計算以下四個步驟來完成驗證。

  • $ w = s^{-1} \mod q $
  • $ u1 = (SHA(M) \times w) \mod q $
  • $ u2 = r \times w \mod q $
  • $ v = g^{u1} y^{u2} \mod q $

test $ v = r $, why correct ?

理論的正確性可以參考以下證明,把公開的 $y = g^x \mod p$ 拿出來,這個安全性取決於離散對數的算法難度。

  • known $y = g^x \mod p $, $x$ is a private key
  • because $s = k^{-1}(SHA(M) + x \times r) \mod q$
  • then $k = s^{-1}(SHA(M) + x \times r) \mod q$

want $r = (g^k \mod p) \mod q$

  • $r = (g^k \mod p) \mod q = g^{s^{-1}(SHA(M) + x \times r)} \mod q$
  • $r = g^{s^{-1} SHA(M)} \times g^{x (s^{-1} \times r)} \mod q $
  • $r = g^{s^{-1} SHA(M)} \times g^{x^{(s^{-1} \times r)}} \mod q $
  • $r = g^{s^{-1} SHA(M)} \times y^{(s^{-1} \times r)} \mod q $

而我們事實上就是在處理指數部分,

離散對數

解決問題 $y = g^x \mod p$,當已知 $y, g, p$,要解出 $x$ 的難度大為提升,不像國高中學的指數計算,可以藉由 log() 運算來完成,離散對數可以的複雜度相當高,當 $p$ 是一個相當大的整數時,通常會取用到 256 bits 以上,複雜度則會在 $O(2^{100})$ 以上。

實際上有一個有趣的算法 body-step, giant-step algorithm,中文翻譯為 小步大步算法 ,在 ACM-ICPC 競賽中也可以找到一些題目來玩玩,算法的時間複雜度為 $O(\sqrt{p})$,空間複雜度也是 $O(\sqrt{p})$。相信除了這個外,還有更好的算法可以完成。

小步大步算法其實很類似塊狀表的算法,分塊處理,每一塊的大小為 $\sqrt{p}$,為了找尋答案計算其中一塊的所有資訊,每一塊就是一小步,接著就是利用數學運算,拉動數線,把這一塊往前推動 (或者反過來把目標搜尋結果相對往塊的地方推動)。因此需要走 $\sqrt{p}$ 大步完成。

Read More +

虛擬實境 考古筆記

虛擬實境這一門課可以讓你體驗有如在上另一門課的虛擬實境,前半段講生物結構,來了解軟硬體需要那些特性、效能,後半段在講視覺建模算法,牽涉到計算幾何、線性代數 … 等課程。

在幾何課程中介紹的 Delaunay triangulation 對於建模算法是相當重要,接著在多重解析度部分牽涉到傳輸跟處理速度,在資料結構的儲存上很有趣。為了加速運算效能、漸進式需求,各種轉換壓縮都會跑出來。

複習

  1. (15%)[Definition]
    詳細說明一個虛擬實境 (virtual reality) 系統需要具備那些特性,並說明一個虛擬實境系統要用到哪些軟體技術。

    提供人們能感知的動態環境,視覺、嗅覺、觸覺,並且能與環境互動中得到反饋,得到逼真感覺。需要以下幾個軟體

    1. 影像處理與繪製
    2. 物理引擎
    3. 擷取資料的整合
  2. (15%)[Human perception]
    說明人類 1. 單眼立體視覺的原理,2. 雙眼立體視覺的原理;並3. 比較其優缺點。

    1. 單眼立體視覺靠單一眼球調整焦距,將數張影像交給大腦來整合,這必須藉由對物體認知來補足。
    2. 雙眼立體視覺的原理主要靠左右兩個眼球不同的相位差,來計算立體影像的距離差距。
    3. 視野範圍以及反應時間,單眼必須藉由調整焦距來補足,雙眼可以同時獲取不同相位差的影像。
  3. (15%)[Hardware]
    說明感應手套 (sensing glove) 與力回饋手套 (force-feedback glove) 的原理,並比較他們在應用上的差別(可舉例說明)。

    感應手套是由操作者的反應傳輸給感測器,藉由手套上感應器之間的變化數據得到操作者給予的狀態。力回饋手套則是將物理特性反饋給使用者,藉由手套上的機械,施予使用者力反饋。

    感應手套為使用者操作,力反饋手套則相反由環境操作。

  4. (15%)[Modeling]
    說明五種 3D 模型的表面表示式 (surface representation),並比較這些表示式的優缺點。

    目前共計主要分成五種表示法:

    • regular square grid meshes (RSG)

    • triangulated regular meshes (TRM)

    • triangulated irregular networks (TINs)

    • polygon meshes

    • parametric patches

      RSG 建造簡單,但會有四點不共平面,影響到貼圖繪製上的問題。TRM 解決四點不共平面問題,但與 RSG 問題無法凸顯邊緣。TINs 解決無法凸顯邊緣問題,但需要經過最佳化算法來得到不規則三角網格,儲存方式必須記錄所有的點座標,由於要經過最佳化算法,處理速度會慢上很多。動態更動座標的效果較差。polygon meshes 建造相對困難,多邊形的最小單位是三角形,頂點儲存量比 TINs 少很多,parametric patches 藉由取樣點套入參數,讓表面接近平滑,針對部分訊息的改造,平滑效果造成的反饋比較逼真。

  5. (15%)[Multiresolution modeling]
    說明並比較以小波轉換為基礎的多重解析度模塑 (wavelet-based multiresolution modeling) 技術與 Lindstrom 等人的對稱三角網格 (symmetric triangulated networks) 多重解析度模塑技術的原理及優缺點。

    • wavelet-based multiresolution modeling
      解析度資料量與面積成正比,考慮到與觀察者之間的距離,用另外三張影響轉換到更高解析度,每一次解析度增加兩倍。
    • symmetric triangulated networks
      考慮到地形高地、平坦性對於觀察者的影響,投影到使用者的平面,判斷是否差異過大需要提供更高解析,解析度資料量所需節點數成正比

      相較之下,小波轉換給予的傳輸資料反應會比較慢,同時也會在使用者感受不到差異的地方提供更高解析度,很容易浪費資源,對稱三角網格在資料鏈結會複雜許多,沒有小波轉換的儲存架構來得容易理解,但對稱三角網格反應速度快,支持快速的解析度變換。

  6. (15%)[Multiresolution modeling]
    說明漸進網格 (progressive mesh) 多重解析度模塑與二次誤差 (quadric-error-metric) 多重解析度模塑的相同與差異處,並比較兩者的優缺點。

    漸進網格藉由拔點、縮邊,提供不同解析度,依序拔點,考慮四個因素來決策拔點順序,這四個因素分別為影響到其他點之間的距離、縮邊的長度總和、頂點材質的差異、對邊界影響程度。

    考慮的是從精細模型轉換到粗糙模型的差異變化,挑選變成粗糙的影響最小的先拔除,這樣的計算量會比較大,拔除一點後,鄰近的點的誤差值計算也要跟著重新計算。

    二次誤差類似漸進網格的方式進行拔點縮點,但縮邊完的的結果不會只有三種可能,縮到兩端點的其中一個、或者是中點,提供一個數學計算來找到縮到哪一個地方更好。

    二次誤差考慮的是從端點誤差值得相加,並且產生一個新的頂點,有點霍夫曼編碼的意味在。計算從粗糙模型上的點到精細模型平面的距離平方,與之前的模型 (Progressive mesh - Hoppe) 恰恰相反,並不是考慮精細模型上的點到粗糙模型平面的距離平方。因此對於 QEM 的誤差可以累加。問題是粗糙模型上的頂點是無法得知,屬於未知數,用內插法。QEM 誤差精確度只是約略,速度快但品質沒有 Progressive mesh 來得好。

  1. (15%)[Multiresolution modeling]
    說明 (a) 動態載入 (dynamic loading) 的意義。(b) 動態載入與多重解析度模塑技術整合會產生什麼問題。(c)解決上述問題的三種方法。

    動態載入為在記憶體不足時,將部分資料存放在磁碟中,當程式運行到需要那些資料進行運算時再從磁碟中讀取,這樣的過程稱為動態載入。

    動態載入對於多重解析度塑模來說,主要是記憶體問題以及所需要的解析度跟數據大小關係,解析度度越高,所需要的資料量越多。但是畫面呈現的解析度不一下,要怎麼進行資料讀取,動態載入的數據結構定義則需要特別設計,例如是要 response time 最小、計算複雜度最低,讀取數量最小 … 等。

    假設需要不同解析度可以靠 Discrete layered multiresolution models,每一種解析度彼此獨立儲存,這樣的方法可以提供基礎的不同解析度,但讀取時間相對長,儲存在磁碟的空間需求大,效果呈現是最快的,但是離散結果會有不連續的解析度變化。

    為了解決解析度連續變化問題,可以使用 progressive mesh 來達到,提供以點、線與解析度呈現線性變化。

    若需要不同解析度,可以利用 hierarchical models 來完成畫面中每一處的解析度不同,這是一種資料結構,當需要更高解析度時,則走訪更深的樹節點來印出所需要的畫面。

    評價效果好壞利用 Delaunay triangulation 計算平滑程度。

  2. (15%)[Rendering]
    說明shading的意義,敘述 Gouraud shading 與 Phong shading 的作法,最後比較他們的優缺點。

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
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 +

遊戲介面設計及效果 Game UI & Effect

前言

上次沒有好好地將遊戲內容進行擷取說明,單純對介面設計原則做說明,介面設計會根據裝置操作有所不同吧,那遊戲介面是否又能如此?

要把法則呈現在遊戲中又是另一回事,畢竟遊戲跟網頁又有點差別,當然也有人認為認知風格跟遊戲性不相干,倒不如說會跟遊戲內容有關,遊戲內容決定介面設計方向,若要根據認知風格設計,想必會是一道難題。

遊戲內容的多寡將決定運行的流程、架構,遊戲介面越簡潔越好,最後的演化就是什麼 都沒有最好 ,除非有人喜歡儀表板,那就另當別論。隨著遊戲必要才出現的介面,會導致程式撰寫相當麻煩,要用排除法來防止奇葩的流程走向。

不僅介面擺設需要考量,還要有觸發時間、方法,以及浮現效果,這些會影響到使用者是否知道可以使用,大部分的介面以文字為主,那串接效果影響不大。為了提升使用者體驗,最好是使用圖示為主,但這會造成跟背景融合的一體感,可能會造成無法被使用者察覺可觸發,必須在圖示效果加點功夫。

就像虛擬實境課程內容所提及的漸進式效果會比較好,對於使用者有如虛擬實境,也就是說介面出現的時間跟方法不能太過迅速。關於上虛擬實境課程的那些小事,有如在上影像處理課程的虛擬實境呢。

以上內容,僅本人觀察。

Github

如果圖片看不到,請至 Github 中的 doc 目錄下查閱本篇附圖。

遊戲執行檔和開源代碼都在其中,當然還有遊戲的編輯器。更多的遊戲截圖在裡頭,只支持 windows 平台。

Animate

Info Demo
Menu Enter/Select Menu
Button Enter Menu
Popup Menu Menu
Menu Focus Menu
Lighting Menu
Fog Menu
Use Props Menu
Caption Menu

Screenshot

選單使用方向鍵和滑鼠點擊操控,同時提供大綱說明。

Info Demo
How to Play Menu1
Story Menu2
Test Room Menu3
Achievement Menu4
Options Menu4

Tutorial Design

使用教學關卡,步入遊戲元素與操作。

Info Demo
Tutorial Tutorial

Game UI Design

遊戲介面細節設計

Info Demo
Tip Tip
Control Information Information
Exit Warning Information
Props View 1 (Scroll up) Letter
Props View 2 (Scroll down) Letter
Level Complete Clear
Level Fail Fail

Game Element

遊戲元素效果

Info Demo
Hide Hide
Use Use
Test Room Test

Option Design

遊戲配置設計

Info Demo
Sound Sound
Difficult Difficult

Story Design

過場 (eyecatch) 劇情的介面設計

Info Demo
Story Story
Story branch 1 Story
Story branch 2 Story branch

Other Mode

遊戲模式設計

Info Demo
Time Mode 1 (Time Count) Time
Time Mode 2 (Time Up) Time
Time Mode 3 (Time Over) Time

Feature

考量的設計並沒全部去實作,提供設計參考。

Info Demo
Old 1 Old 1
Old 2 Old 2
X-Box X-Box
Andriod Android
Hexagon Hexagon
Overview 1 Overview 1
Overview 2 Overview 2

Game Layout

Info Demo
Origin Origin
Clear Clear
Icon With props 1 Icon
Icon With props 2 Icon
Exit Menu Exit Menu

Option Setting

Info Demo
Keyboard 1 Keyboard 1
Keyboard 2 Keyboard 2

Over Layout

Info Demo
Popup popup

Game Flow

Info Demo
With Over over
With Eyecatch eyecatch

Game Caption

Info Demo
Balloon balloon
Read More +

資訊安全 小考 2 筆記

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

Problem

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

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

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

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

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

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

Notes

Part 1

基礎步驟如下

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

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

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

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

Part 2

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

Part 3

Fermat’s theorem :

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

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

Part 4

Euler’s theorem

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

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

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

encrypt a message $M$ the sender:

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

decryption a ciphertext $C$

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

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

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

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

Part 5

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

Part 6

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

  1. $N = p \times q$

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

  3. 套用中國餘式定理 CRT,簡單的模型如下:
    $a_i = M \mod m_i$
    $M = a_1 c_1 + a_2 c_2 + \text{ … } $
    $Mi = m_1 \times m_2 … \times m_n / m_i$
    $c_i = M_i \times (M_i^{-1}) \mod m_i$

  4. 因此 RSA 可以預先處理 $c_p = q \times (q^{-1} \mod p)$ 和 $c_q = p \times (p^{-1} \mod q)$,還原的算法則是 $M = Mp \times c_p + Mq \times c_q \mod N$

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

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

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

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

Read More +

ITSA 2015 第四屆 桂冠賽

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

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

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

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

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

Problem A

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
#include <stdio.h>
#include <vector>
using namespace std;
int A[1024][512];
char s[16];
int main() {
int testcase;
int n, m;
scanf("%d", &testcase);
while (testcase--) {
scanf("%d %d", &n, &m);
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
scanf("%s", s);
A[i][j] = s[0] == 'Y';
}
}
vector<int> p;
for (int i = 0; i < n; i++)
p.push_back(i);
for (int i = 0; i < m; i++) {
int Y = 0;
for (int j = 0; j < p.size(); j++)
Y += A[p[j]][i] == 1;
if (Y == p.size() - Y || Y == 0 || Y == p.size())
continue;
vector<int> np;
for (int j = 0; j < p.size(); j++) {
if (Y < p.size() - Y) {
if (A[p[j]][i] == 1)
np.push_back(p[j]);
} else {
if (A[p[j]][i] == 0)
np.push_back(p[j]);
}
}
p = np;
}
for (int i = 0; i < p.size(); i++) {
printf("%d%c", p[i]+1, i == p.size()-1 ? '\n' : ' ');
}
}
return 0;
}

Problem B

兩個機器人運行的行動和週期不同,用 timeline 的方式去模擬兩台機器人的狀態,利用 time mod (N + E) 來得到當前屬於前 N 還是後 E。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#include <stdio.h>
#include <vector>
using namespace std;
int main() {
int testcase;
int N, M, N1, F1, N2, F2;
int X1, Y1, E1, X2, Y2, E2;
scanf("%d", &testcase);
while (testcase--) {
scanf("%d %d", &M, &N);
scanf("%d %d %d %d %d", &X1, &Y1, &E1, &N1, &F1);
scanf("%d %d %d %d %d", &X2, &Y2, &E2, &N2, &F2);
int has = 0;
for (int i = 0; i <= max(F1, F2); i++) {
if (X1 == X2 && Y1 == Y2) {
printf("robots explode at time %d\n", i);
has = 1;
break;
}
if (i < F1) {
if (i%(N1 + E1) < N1)
Y1 = (Y1 + 1)%N;
else
X1 = (X1 + 1)%M;
}
if (i < F2) {
if (i%(N2 + E2) < E2)
X2 = (X2 + 1)%M;
else
Y2 = (Y2 + 1)%N;
}
}
if (!has)
puts("robots will not explode");
}
return 0;
}

Problem C

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
#include <stdio.h>
#include <vector>
#include <queue>
#include <algorithm>
using namespace std;
const int MAXN = 32767;
vector<int> g[MAXN];
int deg[MAXN], dist[MAXN];
int main() {
int testcase, n, u, v;
scanf("%d", &testcase);
while (testcase--) {
scanf("%d", &n);
for (int i = 0; i < n; i++)
g[i].clear(), deg[i] = 0, dist[i] = -1;
for (int i = 1; i < n; i++) {
scanf("%d %d", &u, &v);
g[u].push_back(v);
g[v].push_back(u);
deg[u]++, deg[v]++;
}
queue<int> Q;
for (int i = 0; i < n; i++)
if (deg[i] == 1)
Q.push(i), dist[i] = 1;
while (!Q.empty()) {
u = Q.front(), Q.pop();
for (int i = 0; i < g[u].size(); i++) {
v = g[u][i];
if (--deg[v] == 1) {
dist[v] = dist[u] + 1;
Q.push(v);
}
}
}
int mx = 0, ret;
for (int i = 0; i < n; i++) {
if (dist[i] > mx)
mx = dist[i], ret = i;
}
printf("%d\n", ret);
}
return 0;
}

Problem D

Bfs 搜索,定義狀態為 dist[x][y][dir] 表示從起點到 (x, y) 時,利用 dir 方向上的門進入的最短路徑,接著按照 Bfs 搜索到目的地。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
#include <stdio.h>
#include <queue>
#include <algorithm>
#include <string.h>
using namespace std;
const int MAXN = 20;
const int dx[] = {0, 1, 0, -1};
const int dy[] = {1, 0, -1, 0};
char sg[MAXN][MAXN][4][8];
int dist[MAXN][MAXN][4];
int toDir(char c) {
switch(c) {
case 'E': return 0;
case 'S': return 1;
case 'W': return 2;
case 'N': return 3;
}
return -1;
}
void bfs(int dir, int sx, int sy, int ex, int ey) {
queue<int> X, Y, D;
memset(dist, 0, sizeof(dist));
X.push(sx), Y.push(sy), D.push(dir);
dist[sx][sy][dir] = 1;
while (!X.empty()) {
sx = X.front(), X.pop();
sy = Y.front(), Y.pop();
dir = D.front(), D.pop();
if (sx == ex && sy == ey) {
printf("%d\n", dist[sx][sy][dir]);
return ;
}
for (int i = 0; sg[sx][sy][dir][i]; i++) {
int d = toDir(sg[sx][sy][dir][i]);
int tx, ty, td;
tx = sx + dx[d], ty = sy + dy[d];
td = (d + 2)%4;
if (dist[tx][ty][td] == 0) {
dist[tx][ty][td] = dist[sx][sy][dir]+1;
X.push(tx), Y.push(ty), D.push(td);
}
}
}
puts("Are you kidding me?");
}
int main() {
int testcase;
int n, m, q, x, y;
scanf("%d", &testcase);
while (testcase--) {
scanf("%d %d", &n, &m);
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
scanf("%d %d", &x, &y);
for (int k = 0; k < 4; k++)
scanf("%s", &sg[x][y][k]);
}
}
scanf("%d", &q);
for (int i = 0; i < q; i++) {
int ex, ey, sx, sy;
char s[8];
scanf("%s %d %d %d %d", &s, &sx, &sy, &ex, &ey);
bfs(toDir(s[0]), sx, sy, ex, ey);
}
puts("----------");
}
return 0;
}

Problem E

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

Problem F

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
#include <stdio.h>
#include <assert.h>
#include <string.h>
#include <set>
#include <vector>
#include <algorithm>
#include <iostream>
using namespace std;
const int MAXP = 1<<21;
const int MAXN = 4;
const int MAXL = 64;
char pwd[MAXN][MAXL];
int pwdLen[MAXN], A[MAXN], n, sumA;
char dp[MAXP];
int arg_dp[MAXP][2];
void dfs(int dep, int idx[], int v) {
if (dep == n) {
int same = 1;
for (int i = 1; i < n; i++)
same &= pwd[i][idx[i]] == pwd[0][idx[0]];
if (same) {
int s = 0;
arg_dp[v][0] = -1, arg_dp[v][1] = pwd[0][idx[0]];
for (int i = 0; i < n; i++) {
if (idx[i] == 0) {
dp[v] = 1;
return ;
}
s = s + (idx[i]-1) * A[i];
}
dp[v] = dp[s] + 1, arg_dp[v][0] = s;
} else {
arg_dp[v][0] = -2;
for (int i = 0; i < n; i++) {
if (idx[i] == 0)
continue;
if (dp[v - A[i]] > dp[v])
dp[v] = dp[v - A[i]];
}
}
return ;
}
for (int i = 0; i < pwdLen[dep]; i++) {
idx[dep] = i;
dfs(dep+1, idx, v + A[dep] * i);
}
}
//
#define MAXCHAR (52 + 10)
#define MAXNODE (131072)
class Trie {
public:
struct Node {
Node *next[MAXCHAR];
int cnt;
set<int> S;
void init() {
cnt = 0, S.clear();
memset(next, 0, sizeof(next));
}
} nodes[MAXNODE];
Node *root;
int size, cases;
Node* getNode() {
assert(size < MAXNODE);
Node *p = &nodes[size++];
p->init();
return p;
}
void init() {
size = cases = 0;
root = getNode();
}
inline int toIndex(char c) {
if (c <= '9') return c - '0';
if (c <= 'Z') return 10 + c - 'A';
if (c <= 'z') return 36 + c - 'a';
assert(false);
}
inline int toChar(int i) {
if (i < 10) return i + '0';
if (i < 36) return i - 10 + 'A';
if (i < 62) return i - 36 + 'a';
assert(false);
}
// basis operation
void insert(const char str[], int w) {
Node *p = root;
for (int i = 0, idx; str[i]; i++) {
idx = toIndex(str[i]);
if (p->next[idx] == NULL)
p->next[idx] = getNode();
p = p->next[idx];
}
p->cnt += w;
}
int find(const char str[]) {
Node *p = root;
for (int i = 0, idx; str[i]; i++) {
idx = toIndex(str[i]);
if (p->next[idx] == NULL)
p->next[idx] = getNode();
p = p->next[idx];
}
return p->cnt;
}
void free() {
return ;
}
} tool;
char s[MAXL];
void dfsLCS(int idx[], int v, Trie::Node *u) {
if (v < 0)
return ;
if (arg_dp[v][0] >= -1) {
int vidx = tool.toIndex(arg_dp[v][1]);
if (u->next[vidx] == NULL)
u->next[vidx] = tool.getNode();
u = u->next[vidx];
if (u->S.count(v))
return;
u->S.insert(v);
if (dp[v] == 1)
return ;
if (arg_dp[v][0] != -1) {
for (int i = 0; i < n; i++)
idx[i]--;
dfsLCS(idx, arg_dp[v][0], u);
for (int i = 0; i < n; i++)
idx[i]++;
}
} else {
if (u->S.count(v))
return;
u->S.insert(v);
for (int i = 0; i < n; i++) {
if (idx[i] == 0)
continue;
if (dp[v - A[i]] == dp[v]) {
idx[i]--;
dfsLCS(idx, v - A[i], u);
idx[i]++;
}
}
}
}
void printLCS(int dep, Trie::Node *u, char *s, vector<string> &ret) {
if (u == NULL) return;
if (dep == -1) {
ret.push_back(s+1);
return;
}
for (int i = 0; i < MAXCHAR; i++) {
*s = tool.toChar(i);
printLCS(dep-1, u->next[i], s-1, ret);
}
}
int countLCS(int dep, Trie::Node *u, char *s) {
if (u == NULL) return 0;
if (dep == -1)
return 1;
int ret = 0;
for (int i = 0; i < MAXCHAR; i++) {
*s = tool.toChar(i);
ret += countLCS(dep-1, u->next[i], s-1);
}
return ret;
}
int main() {
int testcase;
scanf("%d", &testcase);
while (testcase--) {
scanf("%d", &n);
for (int i = 0; i < n; i++) {
scanf("%s", pwd[i]);
pwdLen[i] = strlen(pwd[i]);
}
A[0] = 1;
for (int i = 1; i < n; i++)
A[i] = A[i-1] * pwdLen[i-1];
memset(dp, 0, sizeof(char) * A[n-1] * pwdLen[n-1]);
int idx[MAXN], f = A[n-1] * pwdLen[n-1] - 1;
dfs(0, idx, 0);
// printf("%d\n", (int) dp[f]);
tool.init();
memset(s, 0, sizeof(s));
for (int i = 0; i < n; i++)
idx[i] = pwdLen[i]-1;
dfsLCS(idx, f, tool.root);
printf("%d\n", countLCS(dp[f]-1, tool.root, s + dp[f]-1));
vector<string> ret;
printLCS(dp[f]-1, tool.root, s + dp[f]-1, ret);
sort(ret.begin(), ret.end());
for (int i = 0; i < ret.size(); i++)
printf("%s\n", ret[i].c_str());
}
return 0;
}
/*
999
2
abcdabcdabcdabcdefghefghefghefgh
dcbadcbadcbadcbahgfehgfehgfehgfe
2
abcdabcdabcdabcd
dcbadcbadcbadcba
999
2
abcabcaa
acbacba
2
abcdfgh
abccfdsg
3
3124158592654359
3173415926581359
763141578926536359
*/

Problem G

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
#include <stdio.h>
#include <string.h>
#include <math.h>
#include <algorithm>
#include <queue>
#include <stack>
#include <assert.h>
#include <set>
using namespace std;
const int MAXV = 1024;
const int MAXE = MAXV * 200 * 2;
const int INF = 1<<29;
typedef struct Edge {
int v, cap, flow;
Edge *next, *re;
} Edge;
class MaxFlow {
public:
Edge edge[MAXE], *adj[MAXV], *pre[MAXV], *arc[MAXV];
int e, n, level[MAXV], lvCnt[MAXV], Q[MAXV];
void Init(int x) {
n = x, e = 0;
for (int i = 0; i < n; ++i) adj[i] = NULL;
}
void Addedge(int x, int y, int flow){
edge[e].v = y, edge[e].cap = flow, edge[e].next = adj[x];
edge[e].re = &edge[e+1], adj[x] = &edge[e++];
edge[e].v = x, edge[e].cap = 0, edge[e].next = adj[y];
edge[e].re = &edge[e-1], adj[y] = &edge[e++];
}
void Bfs(int v){
int front = 0, rear = 0, r = 0, dis = 0;
for (int i = 0; i < n; ++i) level[i] = n, lvCnt[i] = 0;
level[v] = 0, ++lvCnt[0];
Q[rear++] = v;
while (front != rear){
if (front == r) ++dis, r = rear;
v = Q[front++];
for (Edge *i = adj[v]; i != NULL; i = i->next) {
int t = i->v;
if (level[t] == n) level[t] = dis, Q[rear++] = t, ++lvCnt[dis];
}
}
}
int Maxflow(int s, int t){
int ret = 0, i, j;
Bfs(t);
for (i = 0; i < n; ++i) pre[i] = NULL, arc[i] = adj[i];
for (i = 0; i < e; ++i) edge[i].flow = edge[i].cap;
i = s;
while (level[s] < n){
while (arc[i] && (level[i] != level[arc[i]->v]+1 || !arc[i]->flow))
arc[i] = arc[i]->next;
if (arc[i]){
j = arc[i]->v;
pre[j] = arc[i];
i = j;
if (i == t){
int update = INF;
for (Edge *p = pre[t]; p != NULL; p = pre[p->re->v])
if (update > p->flow) update = p->flow;
ret += update;
for (Edge *p = pre[t]; p != NULL; p = pre[p->re->v])
p->flow -= update, p->re->flow += update;
i = s;
}
}
else{
int depth = n-1;
for (Edge *p = adj[i]; p != NULL; p = p->next)
if (p->flow && depth > level[p->v]) depth = level[p->v];
if (--lvCnt[level[i]] == 0) return ret;
level[i] = depth+1;
++lvCnt[level[i]];
arc[i] = adj[i];
if (i != s) i = pre[i]->re->v;
}
}
return ret;
}
} g;
int main() {
int testcase;
int n, m;
int nx[128], ny[128], mx[128], my[128];
scanf("%d", &testcase);
while (testcase--) {
scanf("%d %d", &n, &m);
for (int i = 0; i < n; i++)
scanf("%d %d", &nx[i], &ny[i]);
for (int i = 0; i < m; i++)
scanf("%d %d", &mx[i], &my[i]);
int source = n+m, sink = n+m+1;
g.Init(n+m+2);
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
if (abs(nx[i]-mx[j]) + abs(ny[i]-my[j]) <= 5)
g.Addedge(i, j+n, 1);
}
}
for (int i = 0; i < n; i++)
g.Addedge(source, i, 1);
for (int i = 0 ; i < m; i++)
g.Addedge(i+n, sink, 1);
int flow = g.Maxflow(source, sink);
printf("%d\n", flow);
}
return 0;
}

Problem H

如果摩擦力和位能動能互換是連續的,那這一題的作法可能就會有問題。很明顯地為了要求出起點到終點的最少能量需求,必然希望最後到終點的能量恰好為 0,因此逆推最少能量消耗,從終點推論到起點。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
#include <stdio.h>
#include <vector>
#include <queue>
#include <algorithm>
using namespace std;
const int MAXN = 8192;
int n, m, st, ed, h[MAXN];
vector< pair<int, int> > g[MAXN];
int dist[MAXN], inq[MAXN];
void spfa(int st, int ed, int n) {
for (int i = 0; i < n; i++)
dist[i] = 0x3f3f3f3f, inq[i] = 0;
queue<int> Q;
int u, v, w;
dist[ed] = 0, Q.push(ed);
while (!Q.empty()) {
u = Q.front(), Q.pop();
inq[u] = 0;
for (int i = 0; i < g[u].size(); i++) {
v = g[u][i].first, w = g[u][i].second;
if (h[v] > h[u]) {
if (dist[v] > max(dist[u] - (h[v] - h[u]) + w, 0)) {
dist[v] = max(dist[u] - (h[v] - h[u]) + w, 0);
if (inq[v] == 0) {
inq[v] = 1;
Q.push(v);
}
}
} else {
if (dist[v] > dist[u] + (h[u] - h[v]) + w) {
dist[v] = dist[u] + (h[u] - h[v]) + w;
if (inq[v] == 0) {
inq[v] = 1;
Q.push(v);
}
}
}
}
}
printf("%d\n", dist[st]);
}
int main() {
int testcase;
int u, v, w;
scanf("%d", &testcase);
while (testcase--) {
scanf("%d %d %d %d", &n, &m, &st, &ed);
st--, ed--;
for (int i = 0; i < n; i++)
g[i].clear();
for (int i = 0; i < n; i++)
scanf("%d", &h[i]);
for (int i = 0; i < m; i++) {
scanf("%d %d %d", &u, &v, &w);
u--, v--;
g[u].push_back(make_pair(v, w));
g[v].push_back(make_pair(u, w));
}
spfa(st, ed, n);
}
return 0;
}

Problem I

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

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

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

Version 1

直接用字串進行檢索。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
#include <stdio.h>
#include <string.h>
#include <algorithm>
#include <map>
#include <iostream>
using namespace std;
int main() {
int testcase, n, m;
scanf("%d", &testcase);
while (testcase--) {
scanf("%d %d", &n, &m);
map< string, map<string, int> > FUZZY[1<<4];
map< string, int > CNT[1<<4];
int ret = 0;
char s[8], s1[8], s2[8], s3[8];
int same = 4 - m, diff = m;
for (int i = 0; i < n; i++) {
scanf("%s", s);
// find pair
for (int j = 0; j < (1<<4); j++) {
if (__builtin_popcount(j) == same) {
int sidx1 = 0, sidx2 = 0;
for (int k = 0; k < 4; k++) {
if ((j>>k)&1)
s1[sidx1++] = s[k];
else
s2[sidx2++] = s[k];
}
s1[sidx1] = '\0', s2[sidx2] = '\0', s3[sidx2] = '\0';
map<string, int> &tfuzzy = FUZZY[j][s1];
int tot = CNT[j][s1], similar = 0;
if (tot == 0) continue;
for(int k = (1<<diff)-1; k > 0; k--) {
for (int p = 0; p < diff; p++) {
if ((k>>p)&1)
s3[p] = s2[p];
else
s3[p] = '_';
}
// cout << s3 << endl;
if (__builtin_popcount(k)%2 == 0)
similar -= tfuzzy[s3];
else
similar += tfuzzy[s3];
}
ret += tot - similar;
// printf("%d -- %d %d\n", tot - similar, tot, similar);
}
}
// add
for (int j = 0; j < (1<<4); j++) {
if (__builtin_popcount(j) == same) {
int sidx1 = 0, sidx2 = 0, sidx3 = 0;
for (int k = 0; k < 4; k++) {
if ((j>>k)&1)
s1[sidx1++] = s[k];
else
s2[sidx2++] = s[k];
}
s1[sidx1] = '\0', s2[sidx2] = '\0', s3[sidx2] = '\0';
map<string, int> &tfuzzy = FUZZY[j][s1];
CNT[j][s1]++;
for(int k = (1<<diff)-1; k > 0; k--) {
for (int p = 0; p < diff; p++) {
if ((k>>p)&1)
s3[p] = s2[p];
else
s3[p] = '_';
}
tfuzzy[s3]++;
}
}
}
}
printf("%d\n", ret);
}
return 0;
}

Version 2

將字串轉數字當作索引值加快速度。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
#include <stdio.h>
#include <string.h>
#include <algorithm>
#include <map>
#include <iostream>
using namespace std;
int toIndex(int x) { // [0...36]
if ('0' <= x && x <= '9')
return x - '0';
if ('A' <= x && x <= 'Z')
return x + 10 - 'A';
return 36;
}
int main() {
int testcase, n, m;
scanf("%d", &testcase);
while (testcase--) {
scanf("%d %d", &n, &m);
map< int, map<int, int> > FUZZY[1<<4];
map< int, int > CNT[1<<4];
int ret = 0;
char s[8], s2[8], s3[8];
int same = 4 - m, diff = m;
for (int i = 0; i < n; i++) {
scanf("%s", s);
// find pair
for (int j = 0; j < (1<<4); j++) {
if (__builtin_popcount(j) == same) {
int sidx1 = 0, sidx2 = 0;
int s1 = 0, s3 = 0;
for (int k = 0; k < 4; k++) {
if ((j>>k)&1)
s1 = s1 * 37 + toIndex(s[k]);
else
s2[sidx2++] = s[k];
}
map<int, int> &tfuzzy = FUZZY[j][s1];
int tot = CNT[j][s1], similar = 0;
CNT[j][s1]++;
for(int k = (1<<diff)-1; k > 0; k--) {
s3 = 0;
for (int p = 0; p < diff; p++) {
if ((k>>p)&1)
s3 = s3 * 37 + toIndex(s2[p]);
else
s3 = s3 * 37 + toIndex('_');
}
if (__builtin_popcount(k)%2 == 0)
similar -= tfuzzy[s3];
else
similar += tfuzzy[s3];
tfuzzy[s3]++;
}
ret += tot - similar;
// printf("%d -- %d %d\n", tot - similar, tot, similar);
}
}
}
printf("%d\n", ret);
}
return 0;
}

Version 3

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
#include <stdio.h>
#include <string.h>
#include <algorithm>
#include <map>
#include <iostream>
using namespace std;
inline int toIndex(int x) { // [0...36]
if ('0' <= x && x <= '9')
return x - '0';
if ('A' <= x && x <= 'Z')
return x + 10 - 'A';
return 36;
}
const int MOD = 1874161; // 37^4
int FUZZY[MOD];
int main() {
int C[16][16] = {};
for (int i = 0; i < 16; i++) {
C[i][0] = 1;
for (int j = 1; j <= i; j++)
C[i][j] = C[i-1][j-1] + C[i-1][j];
}
int testcase, n, m;
scanf("%d", &testcase);
while (testcase--) {
scanf("%d %d", &n, &m);
for (int i = 0; i < MOD; i++)
FUZZY[i] = 0;
int ret = 0;
int same = 4 - m, diff = m;
char s[8];
for (int i = 0; i < n; i++) {
scanf("%s", s);
// find pair
for (int j = 0; j < (1<<4); j++) {
if (__builtin_popcount(j) >= same) {
int s1 = 0;
for (int k = 0; k < 4; k++) {
if ((j>>k)&1)
s1 = s1 * 37 + toIndex(s[k]);
else
s1 = s1 * 37 + toIndex('_');
}
int &r = FUZZY[s1];
int v = __builtin_popcount(j);
if ((v - same)%2 == 0)
ret += r * C[v][same];
else
ret -= r * C[v][same];
r++;
}
}
}
printf("%d\n", ret);
}
return 0;
}

Problem J

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

這裡解釋一下順序性問題

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

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

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

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

Version 1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
#include <stdio.h>
#include <string.h>
#include <iostream>
#include <algorithm>
using namespace std;
// O(n), BWT compress inverse
const int MAXN = 2048;
char s[MAXN];
int K[MAXN], M[MAXN], C[MAXN], N;
int main() {
int testcase, e_pos;
scanf("%d", &testcase);
while (testcase--) {
scanf("%s %d", s, &e_pos), e_pos--;
string L(s), F(s);
N = L.length();
memset(K, 0, sizeof(K));
memset(M, 0, sizeof(M));
memset(C, 0, sizeof(C));
for (int i = 0; i < N; i++) {
C[i] = K[L[i]];
K[L[i]]++;
}
sort(F.begin(), F.end());
for (int i = 0; i < N; i++) {
if (i == 0 || F[i] != F[i-1])
M[F[i]] = i;
}
string T(N, '?');
for (int i = 0; i < N; i++) {
T[N-1-i] = L[e_pos];
e_pos = C[e_pos] + M[L[e_pos]];
}
puts(T.c_str());
}
return 0;
}
/*
2
CGA
3
NNBAAA
4
*/

Version 2

蚯蚓的寫法順推。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include <iostream>
#include <string>
#include <vector>
#include <utility>
#include <algorithm>
static const int MXN = 514514;
int main() {
int t;
std::cin >> t;
while (t--) {
int p;
std::string str;
std::cin >> str >> p;
std::vector<std::pair<char, int>> vec;
for (size_t i = 0; i < str.length(); i++)
vec.push_back(std::make_pair(str[i], i));
std::sort(vec.begin(), vec.end());
std::string ans;
for (int i = p - 1; ans.length() < str.length(); i = vec[i].second)
ans += vec[i].first;
std::cout << ans << std::endl;
}
}

Problem K

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

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

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

註記 邪惡的 $\text{round}(\sqrt{k} \times c)$ ,不小心看成 $\text{round}(\sqrt{k}) \times c$

感謝蚯蚓告知這問題。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
#include <stdio.h>
#include <string.h>
#include <map>
#include <vector>
#include <math.h>
#include <algorithm>
using namespace std;
const int MAXN = 131072;
class SegmentTree {
public:
struct Node {
int cover; // #cover
int L, R; // value in real line, cover [L, R]
int clen; // cover length
void init(int a = 0, int b = 0, int c = 0, int d = 0) {
cover = a, L = b, R = c, clen = d;
}
} nodes[MAXN];
int Y[MAXN];
void pushDown(int k, int l, int r) {
}
void pushUp(int k, int l, int r) {
if (nodes[k].cover > 0) {
nodes[k].clen = nodes[k].R - nodes[k].L;
} else {
if (l == r)
nodes[k].clen = 0;
else
nodes[k].clen = nodes[k<<1].clen + nodes[k<<1|1].clen;
}
}
void build(int k, int l, int r) {
nodes[k].init(0, Y[l], Y[r+1], 0);
if (l == r)
return ;
int mid = (l + r)/2;
build(k<<1, l, mid);
build(k<<1|1, mid+1, r);
pushUp(k, l, r);
}
// operator, add
void addUpdate(int k, int l, int r, int val) {
nodes[k].cover += val;
if (nodes[k].cover > 0) {
nodes[k].clen = nodes[k].R - nodes[k].L;
} else {
if (l == r)
nodes[k].clen = 0;
else
nodes[k].clen = nodes[k<<1].clen + nodes[k<<1|1].clen;
}
}
void add(int k, int l, int r, int x, int y, int val) {
if (x <= l && r <= y) {
addUpdate(k, l, r, val);
return;
}
pushDown(k, l, r);
int mid = (l + r)/2;
if (x <= mid)
add(k<<1, l, mid, x, y, val);
if (y > mid)
add(k<<1|1, mid+1, r, x, y, val);
pushUp(k, l, r);
}
// query
long long r_sum;
void qinit() {
r_sum = 0;
}
void query(int k, int l, int r, int x, int y) {
if (x <= l && r <= y) {
r_sum += nodes[k].clen;
return ;
}
pushDown(k, l, r);
int mid = (l + r)/2;
if (x <= mid)
query(k<<1, l, mid, x, y);
if (y > mid)
query(k<<1|1, mid+1, r, x, y);
pushUp(k, l, r);
}
} tree;
struct Event {
long long x, yl, yr;
int val;
Event(long long a = 0, long long b = 0, long long c = 0, int d = 0):
x(a), yl(b), yr(c), val(d) {}
bool operator<(const Event &a) const {
return x < a.x;
}
};
const int MAXD = 32767;
int Lx[MAXD], Ly[MAXD], Rx[MAXD], Ry[MAXD];
long long X[MAXD], Y[MAXD];
double K[MAXD];
long long areaCoverAll(int N, int Lx[], int Ly[], int Rx[], int Ry[]) {
vector<Event> events;
vector<int> VY;
map<int, int> RY;
for (int i = 0; i < N; i++) {
int x1, x2, y1, y2;
x1 = Lx[i], x2 = Rx[i];
y1 = Ly[i], y2 = Ry[i];
if (x1 < x2 && y1 < y2) {
events.push_back(Event(x1, y1, y2, 1));
events.push_back(Event(x2, y1, y2, -1));
VY.push_back(y1), VY.push_back(y2);
}
}
sort(VY.begin(), VY.end());
VY.resize(unique(VY.begin(), VY.end()) - VY.begin());
for (int i = 0; i < VY.size(); i++) {
tree.Y[i] = VY[i];
RY[VY[i]] = i;
}
if (VY.size() < 2)
return 0;
tree.build(1, 0, VY.size()-2);
sort(events.begin(), events.end());
long long area = 0, prevX = 0;
for (int i = 0, j; i < events.size(); ) {
if (i > 0) {
tree.qinit();
tree.query(1, 0, VY.size()-2, 0, VY.size()-2);
area += (events[i].x - prevX) * tree.r_sum;
}
j = i;
while (j < events.size() && events[j].x <= events[i].x) {
tree.add(1, 0, VY.size()-2, RY[events[j].yl], RY[events[j].yr] - 1, events[j].val);
j++;
}
prevX = events[i].x;
i = j;
}
return area;
}
int main() {
int testcase, cases = 0;
long long W, H;
int N;
double sqrtK[128];
for (int i = 0; i < 128; i++)
sqrtK[i] = sqrt(i);
scanf("%d", &testcase);
while (testcase--) {
scanf("%lld %lld", &W, &H);
scanf("%d", &N);
for (int i = 0; i < N; i++) {
int x, y, k;
scanf("%d %d %d", &k, &x, &y);
if (k < 1 || k > 100)
return 0;
X[i] = x, Y[i] = y, K[i] = sqrtK[k];
}
if (N < 1 || N > 20000 || W <= 0 || H <= 0 || W > 1e+7 || H > 1e+7)
return 0;
double l = 0.4, r = max(W, H), mid;
int ret = -1;
while (fabs(l - r) > 0.1) {
mid = (l + r)/2;
for (int i = 0; i < N; i++) {
Lx[i] = min(max(round(X[i] - K[i] * mid), 0.0), (double) W);
Rx[i] = min(max(round(X[i] + K[i] * mid), 0.0), (double) W);
Ly[i] = min(max(round(Y[i] - K[i] * mid), 0.0), (double) H);
Ry[i] = min(max(round(Y[i] + K[i] * mid), 0.0), (double) H);
}
long long area = areaCoverAll(N, Lx, Ly, Rx, Ry);
if (area == W*H)
r = mid, ret = ceil(mid*2);
else
l = mid;
}
printf("Case %d: %d\n", ++cases, ret);
}
return 0;
}
/*
9999
9 9
1
1 0 0
1 1
1
4 0 0
2
12 8
3
4 2 2
16 8 4
4 2 6
12 8
3
4 2 2
10 8 4
4 2 6
*/

後記

如有錯誤,多多包涵。

Read More +

UVa 1402 - Robotic Sort

Problem

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

Sample Input

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

Sample Output

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

Solution

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
#include <stdio.h>
#include <math.h>
#include <stack>
#include <assert.h>
#include <vector>
#include <map>
#include <algorithm>
using namespace std;
// Unrolled Linked List
const int MAXPILE = 512;
const int MAXN = 131072;
class UnrolledLinkedList{
public:
struct Node {
int v[MAXPILE], vn;
int rev_label, pid;
Node *next;
Node() {
vn = rev_label = 0;
next = NULL;
}
void relax() {
if (rev_label) {
for(int i = 0, j = vn-1; i < j; i++, j--)
swap(v[i], v[j]);
rev_label = 0;
}
}
};
Node *head;
int PSIZE, pid;
int e_pos[MAXN], e_id[MAXN];
void init() {
free();
head = NULL, PSIZE = MAXPILE /2;
pid = 0;
}
Node* getNode() {
Node* p = new Node();
p->pid = pid++;
return p;
}
void remap(Node *u) {
for (int i = 0; i < u->vn; i++)
e_pos[u->v[i]] = i, e_id[u->v[i]] = u->pid;
}
int find(int e_val) {
int pid = e_id[e_val];
int sum_element = 0;
Node *u, *v;
for (u = head; u != NULL && u->pid != pid; u = u->next)
sum_element += u->vn;
// printf("find %d - %d\n", e_val, pid);
assert(u != NULL);
if (u->rev_label)
return sum_element + u->vn - 1 - e_pos[e_val];
else
return sum_element + e_pos[e_val];
}
void set(int A[], int n) {
init();
Node *u, *v;
head = getNode();
u = head, v = NULL;
PSIZE = min(PSIZE, (int) sqrt(n));
for (int i = 0; i < n; i++) {
if (u->vn == PSIZE) {
u->next = getNode();
v = u, u = u->next;
}
u->v[u->vn++] = A[i];
}
for (u = head; u != NULL; u = u->next)
remap(u);
}
void shrinkList() {
Node *u, *v;
u = head;
for (u = head; u != NULL && u->next != NULL; u = u->next) {
if (u->vn + u->next->vn <= 2 * PSIZE) { // merge
v = u->next;
u->relax(), v->relax();
for (int i = u->vn, j = 0; j < v->vn; i++, j++)
u->v[i] = v->v[j];
u->vn += v->vn;
remap(u);
u->next = v->next;
delete v;
}
}
}
void splitNode(Node *u, int left_size) { // length(left) = v
Node *v = getNode();
u->relax();
v->next = u->next;
u->next = v;
v->vn = u->vn - left_size;
for(int i = left_size, j = 0; i < u->vn; i++, j++)
v->v[j] = u->v[i];
u->vn = left_size;
remap(u), remap(v);
}
void reverse(int l, int r) { // [l, r] = [0, n-1]
Node *lptr, *rptr, *u, *v;
Node *lpre, *rpre, *rnext;
int sum_element = 0;
u = head, v = NULL;
for (u = head, v = NULL; u != NULL; v = u, u = u->next) {
if (sum_element < l && l < sum_element + u->vn)
splitNode(u, l - sum_element); // left[...l-1], right[l...]
if (sum_element <= r && r < sum_element + u->vn)
splitNode(u, r - sum_element + 1);
if (sum_element == l)
lptr = u, lpre = v;
if (sum_element + u->vn - 1 == r)
rptr = u, rpre = v;
sum_element += u->vn;
}
// debug();
rnext = rptr->next;
stack<Node*> stk;
for (u = lptr; u != rnext; u = u->next) {
u->rev_label = !u->rev_label;
stk.push(u);
}
if (lpre == NULL) {
head = stk.top();
u = head, stk.pop();
while (!stk.empty()) {
u->next = stk.top(), stk.pop();
u = u->next;
}
u->next = rnext;
} else {
u = lpre;
while (!stk.empty()) {
u->next = stk.top(), stk.pop();
u = u->next;
}
u->next = rnext;
}
shrinkList();
}
void debug() {
Node *u = head;
while (u != NULL) {
printf("%d : %d, ", u->pid, u->rev_label);
for(int i = 0; i < u->vn; i++)
printf("%d ", u->v[i]);
puts("");
u = u->next;
}
puts("====");
}
void free() {
Node *u = head, *v = NULL;
while(u != NULL) {
v = u, u = u->next;
delete v;
}
}
} g;
int A[MAXN];
int main() {
int n;
while (scanf("%d", &n) == 1 && n) {
vector< pair<int, int> > B;
for (int i = 0; i < n; i++) {
scanf("%d", &A[i]);
B.push_back(make_pair(A[i], i));
}
sort(B.begin(), B.end());
map< pair<int, int>, int > C;
for (int i = 0; i < B.size(); i++)
C[B[i]] = i;
for (int i = 0; i < n; i++)
A[i] = C[make_pair(A[i], i)];
g.set(A, n);
// g.debug();
vector<int> ret;
for (int i = 0; i < n; i++) {
int pos = g.find(i);
g.reverse(i, pos);
ret.push_back(pos);
// g.debug();
}
for (int i = 0; i < n; i++)
printf("%d%c", ret[i] + 1, i == n-1 ? '\n' : ' ');
}
return 0;
}
/*
6
3 4 5 1 6 2
4
3 3 2 1
0
10
5 18 19 12 7 12 0 2 11 9
1
4
19
5 17 8 10 13 18 10 5 3 15 2 19 12 10 2 14 18 0 6
12
15 13 7 14 15 7 12 15 4 10 6 3
15
18 7 5 6 5 5 10 9 2 4 9 10 7 13 19
5
3 4 1 1 3
6
8 0 6 2 6 16
7
17 5 12 1 3 9 13
1
8
10
15 19 17 19 17 18 2 12 0 10
10
5 1 14 6 7 12 15 17 5 11
0
*/
Read More +