b419. 公平的硬幣

Problem

一天,liouzhou_101 去打印店裏打印了幾張複習資料,花了 4 毛錢,他給了店主1張塊錢的鈔票,結果店主補了他兩枚硬幣,一個5毛錢,一個 1 毛錢。

在走回宿舍的途中,liouzhou_101 一直在想一個問題,5 毛錢的硬幣和 1 毛錢的硬幣哪個更公平?所謂公平,就是指將一枚硬幣拋擲 1 次,他正面朝上的概率是 12,反面朝上的概率也是 12。

結果他一會去就把1毛錢的硬幣拋了 1000 次, 記下來有 531 次正面朝上。然後他突然說道:“這硬幣不公平!!!”

真的不公平嗎?他希望你計算一下出現這種情況的概率。

Sample Input

1
2
3
4
5
10 5 5
20 6 8
100 40 60
1000 531 531
5000 2345 2456

Sample Output

1
2
3
4
5
0.2461
0.2310
0.9648
0.0037
0.1093

Solution

跟柳柳州互相出題目,已經來到了第 n 天,儘管我出的都是垃圾跟搬運題目,一出題被電得好愉悅,出在多的垃圾題都電不到柳柳州。

在擲硬幣找區間次數 $[a, b]$ 的機率,由於是離散的統計上就只能推組合數學,但又不是曲棍棒的模型 (Hockey Stick Pattern),算了先手動窮舉,用 Stirling’s formula 計算組合,後來發現有幾組數據有問題。

當 n 太大的時候,機率分布就相當稀疏,直接套用數值積分,直接去積分自然分布得到公式稍微困難,利用 simpson 或者是 romberg 去解決,但這樣還會有一個問題,只有在平均值的部分機率非常高 (類似離散的 00000100000),導致自適應辛普森積分會判斷錯誤,提醒剖半積分後測資就正確了不少。似乎合作出了一個可怕的題目,儘管如此,還是得用 mathlab 或者是 wolframalpha 來協助測資的檢驗,剩下的部分就交給柳柳州了。

關於自然分布 (normal distribution):

  • 骰子擲 $n$ 次,命中的機率 $p = 1/2$
  • 期望值 $\mu = np = n/2$
  • 標準差為 $\sigma = \sqrt{np(1-p)} = \sqrt{n/4}$

期望值 $E[x] = \sum_{k=1}^{n} k \binom{n}{k} p^k (1-p)^{n-k}$,消階層之後提出,計算後得到 $E[x] = np$

而標準差 $\sigma$ 先從變異數下手

  • 定義: $Var[x] = E[(x - \mu)^2] = \sum (x - \mu)^2 P[X = x]$
  • 拆解步驟 1: $Var[x] = \sum (x^2 - 2 \mu x + \mu^{2}) P[X = x]$
  • 拆解步驟 2: $Var[x] = E[x^2] - 2 E[x] E[x] + E[x] E[x] = E[x^2] - E[x]^2$
  • 拆解步驟 3: $E[x^2] = E[x(x-1)] + E[x] = n(n-1)p^2 + np$
  • 拆解步驟 4: $Var[x] = np(1-p)$

接下來直接帶入自然分布的公式,參數 $(\mu, \sigma)$,特別小心使用辛普森積分 $[a, b]$ 時,由於問題是離散的,要改變成 $[a - 0.5, b + 0.5]$,防止 $a = b$ 時造成積分錯誤。

特別感謝學弟 firejox 的數學指導。

不久之後,firejox 說道內建函數 erf() 可以解決積分問題,詳見 wiki Gauss error function,因此這一題也可以不寫辛普森積分,套用內建函數在不到 10 行內解決此題。

暴力檢測

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
#include <bits/stdc++.h>
using namespace std;
const long double PI = acos(-1), e = exp(1);
const int MAXN = 100001;
double f[MAXN];
double stirling(int n) {
if (n < MAXN)
return f[n];
return log2(sqrt(2*PI*n)) + n*log2(n/e);
}
double logC(int n, int m) {
return stirling(n) - stirling(n - m) - stirling(m);
}
int main() {
f[0] = 0;
for (int i = 1; i < MAXN; i++)
f[i] = log2(i) + f[i-1];
int n, a, b;
int cases = 0;
while (scanf("%d %d %d", &n, &a, &b) == 3) {
double ret = 0;
for (int i = a; i <= b; i++)
ret += pow(2, logC(n, i) - log2(2) * n);
printf("%.4lf\n", ret);
if (++cases > 100)
break;
}
return 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
#include <bits/stdc++.h>
using namespace std;
const long double PI = acos(-1), e = exp(1);
const int MAXN = 1001;
double f[MAXN];
double stirling(int n) {
if (n < MAXN)
return f[n];
return log2(sqrt(2*PI*n)) + n*log2(n/e);
}
double logC(int n, int m) {
return stirling(n) - stirling(n - m) - stirling(m);
}
class SimpsonIntegral {
public:
const double eps = 1e-6;
const double pi = acos(-1);
const double sqr2pi = sqrt(2 * pi);
double mu, sigma; // function coefficient
double f(double x) { // usually replace
return exp(-(pow(x - mu, 2)/2.0/sigma/sigma))/ sigma / sqr2pi;
}
void setCoefficient(double m, double s) {
this->mu = m, this->sigma = s;
}
double simpson(double a, double b, double fa, double fb, double fab) {
return (fa + 4 * fab + fb) * (b - a) / 6.0;
}
double integral(double l, double r) {
return integral(l, r, eps);
}
private:
double integral(double a, double b, double c, double eps, double A, double fa, double fb, double fc) {
double ab = (a + b)/2, bc = (b + c)/2;
double fab = f(ab), fbc = f(bc);
double L = simpson(a, b, fa, fb, fab), R = simpson(b, c, fb, fc, fbc);
if (fabs(L + R - A) <= 15 * eps)
return L + R + (L + R - A) / 15.0;
return integral(a, ab, b, eps/2, L, fa, fab, fb) + integral(b, bc, c, eps/2, R, fb, fbc, fc);
}
double integral(double a, double b, double eps) {
double ab = (a + b) /2;
double fa = f(a), fb = f(b), fab = f(ab);
return integral(a, ab, b, eps, simpson(a, b, fa, fb, fab), fa, fab, fb);
}
} tool;
double binom(double n, double a, double b) {
double a1, b1;
double d = sqrt(n/2.0);
a1 = 0.5 * (1.0 + erf((a - n/2.0 - 0.5) / d));
b1 = 0.5 * (1.0 + erf((b - n/2.0 + 0.5) / d));
return b1 - a1;
}
int main() {
f[0] = 0;
for (int i = 1; i < MAXN; i++)
f[i] = log2(i) + f[i-1];
int n, a, b;
int cases = 0;
while (scanf("%d %d %d", &n, &a, &b) == 3) {
cases++;
if (n < MAXN) {
double ret = 0;
for (int i = a; i <= b; i++)
ret += pow(2, logC(n, i) - log2(2) * n);
printf("%.4lf\n", ret);
} else {
// tool.setCoefficient(n/2.0, sqrt(n/4.0));
// double ret = 0, l = a - 0.5, r = b + 0.5;
// if (r < n/2.0 || l > n/2.0)
// ret = tool.integral(l, r);
// else
// ret = tool.integral(l, n/2.0) + tool.integral(n/2.0, r);
// printf("%.4lf\n", ret);
printf("%.4lf\n", binom(n, a, b));
}
}
return 0;
}
Read More +

b417. 區間眾數

Problem

背景

大學上課是怎麼回事的呢?談論到學期成績計算,很多奇聞軼事可以說的,例如教授任選三題批改、同學任選三題作答、滿分三百、沒有考試、 … 等。筆試類型的考試只是最常見的一種,還有所謂的多才華加分,例如上台報告、舉手發問、參與展覽、參加競賽、 … 等。

不幸地,某 M 修了一堂怪課,教授每一堂課結束後總是會大聲宣揚「葛萊分多加 5 分」對於需要不斷複習演練的某 M 而言,這門課的即時評分方式讓其非常挫敗。神奇的是,有一次教授這麼問同學「給一序列,請計算眾數、平均數、中位數,答對加分。」某 M 剎那間傻住,大學的學期成績可以用這種問題獲得,當他在懷疑這個問題時,問題早已被同學回答走。

某 M 非常不甘心,計算眾數、平均數、中位數就能夠加分,要求只是 n = 10 的序列。既然加分有理,出題吧!

題目描述

平均數、中位數可以藉由區間總和、區間 K 小問題來完成,現在來解決眾數。

給定一個長度為 $n$ ( $1 \le n \le 100000$ ) 的正整數序列 $s$ ($1 \le s_i \le n$),對於 $m$ ( $1 \le m \le 1000000$) 次詢問 $l, r$,每次輸出 $[s_l, \text{... },s_r]$ 中,出現最多次的次數以及有多少種數字出現最多次。

Sample Input

1
2
3
4
5
6
7
8
9
10
11
12
10 10
2 3 1 1 1 2 1 2 1 1
5 8
1 10
6 9
5 9
1 5
3 10
1 9
1 1
6 9
2 3

Sample Output

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

Solution

眾數區間查找 Range mode query 性質可以參考維基百科。

在這一題中,這裡提供三種解法,但我的寫法中只有莫隊算法可以通過,其餘算法受到時間跟空間上的限制,需要額外的優化,細節就不多做說明:

  • 莫隊算法
    只能離線處理,無法強制在線詢問,維護眾數最高頻率指針,來統計眾數的對應次數和等價眾數的個數,由於指針移動都是 $O(1)$,整體上時間效能為 $O(N^{1.5})$
  • 分塊在線 1
    離線預處理 $O(N^{0.5} \times N^{0.5} \times \log{N})$
    提供在線詢問 $O(\text{ans} \times \log{N})$,由於知道每一個塊中的代表眾數都可能是答案,因此預處理每一個塊的眾數,其餘的非眾數捨棄掉,最慘情況是每個數字都出現一次,在線詢問時複雜度會掉到 $O(N \log N)$
  • 分塊在線 2
    • 離線預處理時間 $O(N \times L^2)$,提供在線詢問 $O(N/L)$,其中 $L$ 表示有多少個分塊。
    • 預處理分塊編號 $PILE[i, j]$ 的眾數情況 (類似莫隊維護指針),因此記憶體消耗 $O(N \times L^2)$
    • 對於每一處詢問,會對應預處理中的區域塊的紀錄 $PILE[L, R]$,接著頭尾兩端的殘存數字再依序加入,意即 $A[a, L-1]$$A[R+1, b]$ 的數字加入的複雜度 $O(1)$,隨後再進行撤銷 $O(1)$
    • 假設詢問次數 $Q$$N$ 呈現性關係,整體需要 $O(N \times L^2 + N^2 / L)$,則 $L = N^{1/3}$ 是最好的情況,意即每一個塊具有 $O(N^{2/3})$ 個數字,最後得到整體時間效能為 $O(N^{1.66})$

由於這一題目標不是找到眾數為何 (這牽涉到最小字典順序),而是找眾數的出現個數和有幾種眾數可能,莫隊算法會快於其他算法。若是要求最小眾數莫隊算法需要 $O(N^{1.5} \log{N})$,分塊在線 1 也是 $O(N^{1.5} \log{N})$

分塊在線 2 在此題不僅僅遇到記憶體過大,只好鬆弛 $L$ 來壓低記憶體用量,但增加時間需求,時間上慢個 10 倍以上。

莫隊算法

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
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 100005;
const int MAXV = 100005;
const int MAXQ = 1000005;
class Offline {
public:
struct Event {
static int PILE, belong[MAXV];
int l, r, qid;
Event(int a = 0, int b = 0, int e = 0):
l(a), r(b), qid(e) {}
bool operator<(const Event &x) const {
if (belong[l] != belong[x.l])
return l < x.l;
return r < x.r;
}
} event[MAXQ];
int A[MAXN], N, qsize;
int ret[MAXQ][2];
void init(int N) {
this->N = N, qsize = 0;
memset(val_count, 0, sizeof(val_count));
memset(mode_freq, 0, sizeof(mode_freq));
mode_idx = 0;
Event::PILE = sqrt(N);
for (int i = N; i >= 1; i--)
Event::belong[i] = i / Event::PILE;
}
void q_add(int l, int r) {
event[qsize] = Event(l, r, qsize);
qsize++;
}
void run() {
sort(event, event+qsize);
int l = 1, r = 0;
for (int i = 0; i < qsize; i++) {
while (r < event[i].r) r++, update(A[r], 1);
while (r > event[i].r) update(A[r], -1), r--;
while (l > event[i].l) l--, update(A[l], 1);
while (l < event[i].l) update(A[l], -1), l++;
ret[event[i].qid][0] = mode_idx;
ret[event[i].qid][1] = mode_freq[mode_idx];
}
}
private:
// unrolled
int val_count[MAXV], mode_freq[MAXN], mode_idx;
inline void update(int x, int val) {
if (val == 1) {
mode_freq[val_count[x]]--;
val_count[x]++;
mode_freq[val_count[x]]++;
if (mode_freq[mode_idx+1] != 0)
mode_idx++;
} else if (val == -1) {
mode_freq[val_count[x]]--;
val_count[x]--;
mode_freq[val_count[x]]++;
if (mode_freq[mode_idx] == 0)
mode_idx--;
}
}
} off;
int Offline::Event::PILE = 16, Offline::Event::belong[MAXV];
namespace mLocalStream {
class M {
public:
static const int N = 1048576;
char buf[N], *p, *end;
M() {
p = buf, end = buf + N - 1;
}
void printInt(int x) {
static char stk[16];
int idx = 0;
stk[idx++] = '\n';
if (!x)
stk[idx++] = '0';
while (x)
stk[idx++] = x%10 + '0', x /= 10;
while (idx) {
if (p == end) {
*p = '\0';
printf("%s", buf), p = buf;
}
*p = stk[--idx], p++;
}
}
static inline int readchar() {
static char buf[N];
static char *p = buf, *end = buf;
if(p == end) {
if((end = buf + fread(buf, 1, N, stdin)) == buf) return EOF;
p = buf;
}
return *p++;
}
static inline int ReadInt(int *x) {
static char c, neg;
while((c = readchar()) < '-') {if(c == EOF) return 0;}
neg = (c == '-') ? -1 : 1;
*x = (neg == 1) ? c-'0' : 0;
while((c = readchar()) >= '0')
*x = (*x << 3) + (*x << 1) + c-'0';
*x *= neg;
return 1;
}
~M() {
*p = '\0';
printf("%s", buf);
}
} bprint;
}
int main() {
int n, m, l, r, a, b;
while (mLocalStream::M::ReadInt(&n)) {
mLocalStream::M::ReadInt(&m);
off.init(n);
for (int i = 1; i <= n; i++)
mLocalStream::M::ReadInt(&off.A[i]);
for (int i = 0; i < m; i++) {
mLocalStream::M::ReadInt(&l);
mLocalStream::M::ReadInt(&r);
off.q_add(l, r);
}
off.run();
for (int i = 0; i < m; i++)
printf("%d %d\n", off.ret[i][0], off.ret[i][1]);
}
return 0;
}

分塊在線 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
87
88
89
90
91
92
93
94
95
96
97
98
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 100005;
class ModeQuery {
public:
int A[MAXN], N, PILE, belong[MAXN];
vector< vector<int> > V, Vmode;
vector<int> POS[MAXN];
int q_dup[MAXN], q_cases;
void make(int n) {
N = n, q_cases = 0;
for (PILE = 1; PILE * PILE < N; PILE <<= 1);
V.clear(), Vmode.clear();
for (int l = 0, r; l < N; l = r+1) {
r = min(l + PILE - 1, N-1);
V.push_back(vector<int>(A+l, A+r+1));
Vmode.push_back(vector<int>());
}
for (int i = 0; i < V.size(); i++) {
sort(V[i].begin(), V[i].end());
int mx_cnt = 1, cnt = 1;
for (int j = 1; j <= V[i].size(); j++) {
if (j == V[i].size() || V[i][j] != V[i][j-1]) {
if (cnt > mx_cnt)
mx_cnt = cnt, Vmode[i].clear();
if (cnt == mx_cnt)
Vmode[i].push_back(V[i][j-1]);
cnt = 1;
} else {
cnt++;
}
}
}
for (int i = 1; i <= N; i++) // value range
POS[i].clear(), q_dup[i] = 0;
for (int i = 0; i < N; i++)
POS[A[i]].push_back(i);
for (int i = N-1; i >= 0; i--)
belong[i] = i / PILE;
}
int compute_cnt(int x, int l, int r) {
if (q_dup[x] == q_cases)
return 0;
q_dup[x] = q_cases;
int va = (int) (lower_bound(POS[x].begin(), POS[x].end(), l) - POS[x].begin()) - 1;
int vb = (int) (upper_bound(POS[x].begin(), POS[x].end(), r) - POS[x].begin()) - 1;
return vb - va;
}
void query(int l, int r, int &a, int &b) {
static int t;
q_cases++;
a = b = 0;
if (belong[l] == belong[r]) {
for (int i = l; i <= r; i++) {
t = compute_cnt(A[i], l, r);
if (t > a) a = t, b = 0;
if (t == a) b++;
}
return ;
}
for (int i = belong[l]+1; i < belong[r]; i++) {
for (int j = 0; j < Vmode[i].size(); j++) {
t = compute_cnt(Vmode[i][j], l, r);
if (t > a) a = t, b = 0;
if (t == a) b++;
}
}
for (int i = (belong[a]+1)*PILE-1; i >= l; i--) {
t = compute_cnt(A[i], l, r);
if (t > a) a = t, b = 0;
if (t == a) b++;
}
for (int i = (belong[b])*PILE; i <= r; i++) {
t = compute_cnt(A[i], l, r);
if (t > a) a = t, b = 0;
if (t == a) b++;
}
}
} dream;
int main() {
int n, m, l, r, a, b;
while (scanf("%d %d", &n, &m) == 2) {
for (int i = 0; i < n; i++)
scanf("%d", &dream.A[i]);
dream.make(n);
for (int i = 0; i < m; i++) {
scanf("%d %d", &l, &r);
l--, r--;
dream.query(l, r, a, b);
printf("%d %d\n", a, b);
}
}
return 0;
}

分塊在線 2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 100005;
const int MAXS = 35;
class ModeQuery {
public:
struct Kit {
unsigned short cnt[MAXN], freq[MAXN];
int mode_idx;
Kit() {
init();
}
void init() {
mode_idx = 0;
}
void add(int x, int val) {
freq[cnt[x]]--;
cnt[x] += val;
freq[cnt[x]]++;
if (cnt[x] > mode_idx)
mode_idx = cnt[x];
}
void resume(int x, int val) {
freq[cnt[x]]--;
cnt[x] -= val;
freq[cnt[x]]++;
}
} f[MAXS * MAXS/2], kit;
int fr[MAXS][MAXS];
int A[MAXN], N, PILE, belong[MAXN];
vector< pair<int, int> > V;
void heavy() {
int ff = 0;
for (int i = 0; i < V.size(); i++)
for (int j = i; j < V.size(); j++)
fr[i][j] = ff++;
for (int i = 0; i < V.size(); i++)
for (int j = 0; j < i; j++)
fr[i][j] = ff;
for (int i = 0; i < V.size(); i++) {
f[fr[i][i]].init();
for (int j = V[i].first; j <= V[i].second; j++)
f[fr[i][i]].add(A[j], 1);
}
for (int i = 0; i < V.size(); i++) {
for (int j = i+1; j < V.size(); j++) {
f[fr[i][j]] = f[fr[i][j-1]];
for (int k = V[j].first; k <= V[j].second; k++)
f[fr[i][j]].add(A[k], 1);
}
}
}
void make(int n) {
N = n;
PILE = (int) pow(n, 0.70) + 1;
V.clear();
for (int l = 0, r; l < N; l = r+1) {
r = min(l + PILE - 1, N-1);
V.push_back(make_pair(l, r));
}
for (int i = N-1; i >= 0; i--)
belong[i] = i / PILE;
heavy();
}
void query(int l, int r, int &a, int &b) {
int bl = belong[l], br = belong[r];
if (bl == br || r - l <= PILE + 10) {
kit.init();
for (int i = l; i <= r; i++)
kit.add(A[i], 1);
a = kit.freq[kit.mode_idx], b = kit.mode_idx;
for (int i = l; i <= r; i++)
kit.resume(A[i], 1);
return ;
}
if (l >= 0 && belong[l-1] == bl)
bl++;
if (r < N && belong[r+1] == br)
br--;
Kit &tmp = f[fr[bl][br]];
int tmp_mode_idx = tmp.mode_idx;
for (int i = bl*PILE - 1; i >= l; i--)
tmp.add(A[i], 1);
for (int i = (br+1)*PILE; i <= r; i++)
tmp.add(A[i], 1);
a = tmp.freq[tmp.mode_idx], b = tmp.mode_idx;
for (int i = bl*PILE - 1; i >= l; i--)
tmp.resume(A[i], 1);
for (int i = (br+1)*PILE; i <= r; i++)
tmp.resume(A[i], 1);
tmp.mode_idx = tmp_mode_idx;
}
} dream;
int main() {
int n, m, l, r, a, b;
while (scanf("%d %d", &n, &m) == 2) {
for (int i = 0; i < n; i++)
scanf("%d", &dream.A[i]);
dream.make(n);
for (int i = 0; i < m; i++) {
scanf("%d %d", &l, &r);
l--, r--;
dream.query(l, r, a, b);
printf("%d %d\n", b, a);
}
}
return 0;
}
Read More +

b418. 八成真物

Problem

「偽物比真物更有價值」—貝木泥舟
「真物和偽物一樣有價值」—忍野咩咩
「偽娘比真娘更有價值」—精英島民

任兩個物品的相似度$sim(A, B) = \frac{|A \cap B|}{|A \cup B|}$,換句話說把 $A$ 具有的特徵和 $B$ 具有的特徵類型取交集、聯集個數,相除就能得到其相似度。例如有 5 個特徵,若 A 表示成 11001、B 表示成 01100$sim(A, B) = \frac{|{2}|}{|{1, 2, 3, 5}|} = 0.25$

現在盤面上有 N 個物品、M 種特徵,請問相似度大於等於 0.8 的相似對數 $S$ 有多少種。為了讓這一題更有趣味,算法允許偽物,輸出$\frac{S}{N(N-1)/2} \times 100$

Sample Input

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
8 10
0111101101
0111101100
0111101100
0111101101
1100101010
0111101100
0111101100
0011101111
8 10
1111101100
1011101100
1010101100
1010111010
0100110100
1011101100
1110110000
1011111100

Sample Output

1
2
53.57
25.00

Solution

利用 std::bitset 加速交集和聯集運算,將數個屬性壓縮到一個 32-bits 單位,然後藉 bitcount 盡可能在 $O(1)$ 的時間內完成,就有機會通過這一題。一般的 $O(N^2 M)$$O(N^2 M/32)$ 慢個五六倍。

此外,特別小心浮點數計算誤差,5.0/4.0 >= 0.8 == false,用乘法判定大於閥值的操作。

關於 LSH 的部分,還在進行測試,若 signature 計算太慢,還不如直接暴力法,若能分散找到 signature 或者是預先有辦法處理好,那分桶計算就會好一點。

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
#include <bits/stdc++.h>
using namespace std;
int main() {
int n, m;
char s[1024];
while (scanf("%d %d", &n, &m) == 2) {
bitset<512> attr[n];
for (int i = 0; i < n; i++) {
scanf("%s", s);
attr[i] = bitset<512>(s);
}
double ret = 0;
for (int i = 0; i < n; i++) {
for (int j = i+1; j < n; j++) {
int a = (attr[i]&attr[j]).count();
int b = (attr[i]|attr[j]).count();
if (5 * a >= 4 * b)
ret++;
}
}
printf("%.2lf\n", ret * 100 / (n * (n-1)/2));
}
return 0;
}
Read More +

b413. 虛擬女友

Problem

曾經有一部採訪影片《BBC 紀錄片:別和日本人談性 No Sex. We’re Japanese.》在網路上流傳,其中有一段談到虛擬遊戲中的生活,不少男性以遊戲中的女角發生關係,其中以一款遊戲「Love Plus」為大宗,即使是擁有現實和虛擬的生活,若要選擇其中一方站,不少男性仍然「我選擇死亡」回答。

現在先不解決男女雙方的問題、在彼此關係上要如何運作,如何回到早些年沒有遊戲機、沒有網路、更沒有虛擬女友的交際生活,只有同性朋友要怎麼交流呢?於是有一場專門為這些男性舉行的一場交友會,規則如下所述:

  • 一開始全場男性都彼此不認識
  • 每一個男性分別喜歡各自的虛擬女友 (可以是同一個)
  • 每一個時刻,其中兩個男性會因談論遊戲而成為朋友
  • 自己朋友的朋友也會成為朋友
  • 如果朋友的虛擬女友的類型不同,他們有可能會因偏好女友類型不同而爭吵,稱為不穩定對。

請問每一個時刻下,有多少穩定對朋友。

Sample Input

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

Sample Output

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

Solution

IPSC 2015 F 那一題發生於男女雙方都會進行聯集,而這一題只有男方會進行聯集。

合併兩團男時,只有下標 (女友類型) 相同會成為穩定對,可以利用合併排序來完成,能發生合併表示男的彼此之間不認識,只要考慮有多少女方相同即可。每一次將小堆合併到大堆中,由於要計數合併複雜度需要 $O(min(|A|, |B|) \times \log N)$,若搭配 hash 可以降到 $O(min(|A|, |B|)$。一般并查集 joint 複雜度為 $O(1)$,整體操作只需要 $O(N)$,但為了要計數,整體的複雜度為 $O(N \log N)$

可以選擇合併排序來完成,每一次把兩堆的女方列出來,把兩堆相同類型的次數相乘,列出來可以循序做,或者是串列完成,每一次保證堆裡面的女方順序是由小排到大,合併複雜度就為 $O(|A| + |B|)$,均攤下複雜度為 $O(N \log N)$,當測資不夠隨機複雜度會掉到 $O(N^2)$,情況是每一次只把大小為 1 的堆合併到大小 i-1 的堆。

在測資隨機生成下,第二種作法會來得比第一種快。第一種做法要搭配 ordered_map 或者是 unordered_map,寫起來會比較不方便。

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
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 65536;
vector<int> son[MAXN];
int parent[MAXN], A[MAXN];
int main() {
int testcase, n, m;
scanf("%d", &testcase);
while (testcase--) {
scanf("%d %d", &n, &m);
for (int i = 0; i < n; i++)
scanf("%d", &A[i]);
long long tot_pair = 0;
int x, y;
map< pair<int, int>, int > R;
for (int i = 0; i < n; i++) {
parent[i] = i;
son[i].resize(1, i);
R[pair<int, int>(i, A[i])] = 1;
}
for (int i = 0; i < m; i++) {
scanf("%d %d", &x, &y);
x--, y--;
x = parent[x], y = parent[y];
if (x != y) { // merge small to large
if (son[x].size() > son[y].size())
swap(x, y);
for (auto &e: son[x]) {
pair<int, int> t(parent[e], A[e]);
R[t]--;
if (R[t] == 0) R.erase(t);
parent[e] = y;
son[y].push_back(e);
tot_pair += R[pair<int, int>(parent[e], A[e])];
}
for (auto &e: son[x])
R[pair<int, int>(parent[e], A[e])]++;
son[x].clear();
}
printf("%lld\n", tot_pair);
}
}
return 0;
}
Read More +

b416. 誤會妹子序列

Problem

背景

在 2015 年 6 月的 Facebook 上,不少以「靠北」為前綴命名的粉絲頁面,就如 靠北工程師 為例,不少業界工程師匿名在上面抱怨工作過時、主管、生活 … 等。突然有一篇 #靠北工程師7257 抱怨著編程競賽的零零種種,從回應中能明白存在業界工程師不清楚編程競賽,以 UI 介面、cmd 指令 … 等方式理解比賽的目標、運行。

網路處處有答案,演算法和資料結構到底重不重要?抄下來能用就行,大部分的要求是不講求效率的,但對於曾經在編程競賽待過一陣子的小夥伴而言,看到他們的理解方式開始感到憤憤不平,進行了一陣子的爭吵。

過一陣子,大批的學生開始進入 靠北工程師 裡發文,開始爭論學校、系所哪個是比較有未來的,突然間有一位資管系的同學發問這一道算法題 ‪#‎靠北工程師7780‬ ,結果一不小心就看錯題目,把種類數誤看成個數,於是討論了不少其他的算法來完成,現在就把這個問題交給你。

用各種算法解決這個誤解的題目。

問題描述

Autumn 和 Bakser 又在研究 Gty 的妹子序列了!

但他們遇到了一個難題,對於一段妹子們,他們想讓你幫忙求出這之內美麗度 $s_i \in [a, b]$ 的妹子個數。為了方便,我們規定妹子們的美麗度全都在 $[1,n]$ 中。

給定一個長度為 $n$ ( $1 \le n \le 100000$ ) 的正整數序列 $s$ ($1 \le s_i \le n$),對於 $m$ ( $1 \le m \le 1000000$) 次詢問 $l, r, a, b$,每次輸出 $[s_l, \text{... },s_r]$ 中,權值 $s_i \in [a, b]$ 的個數。

Sample Input

1
2
3
4
5
6
7
8
9
10
11
12
10 10
4 4 5 1 4 1 5 1 2 1
5 9 1 2
3 4 7 9
4 4 2 5
2 3 4 7
5 10 4 4
3 9 1 1
1 4 5 9
8 9 3 3
2 2 1 6
8 9 1 4

Sample Output

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

Solution

相較於 b414. BZOJ 3809 Gty 的二逼妹子序列 解法會相對多樣,單純計數就會變得比較簡單。

在這一題中,這裡提供三種解法:

  • 莫隊算法
    只能離線處理,無法強制在線詢問,算法複雜度由分塊降到 $O(N^{1.5})$。可參考 b414 的解法。
  • 掃描線 + BIT
    只能離線處理,把題目轉換成二維情況 $(i, A[i])$,詢問相當於找到矩形內部 $[l, r] \times [a, b]$ 有多少點座標,所以可以當作二維前綴來完成計數 $f(l, r, a, b) = f(r, b) - f(l-1, b) - f(r, a-1) - f(l-1, a-1)$$f(x, y)$ 表示從原點 $(0, 0)$$(x, y)$ 的點數量,這樣的方式記憶體過多,利用掃描線算法降下來,對 x 由小掃描到大,依序將點插入詢問 $f(x, y)$ 會在掃瞄到 x 時,詢問 $[0, y]$ 之間的累計個數得到,最後將每一個詢問離線成四個詢問,掃描一次即可。
  • 可持久化線段樹
    必須預處理,支持在線詢問,不帶操作修改。將序列權值排序 $(A[i], i)$,依序索引值插入線段樹,紀錄每一個插入完的線段樹狀態,接著當詢問 $l, r, a, b$,相當於在版本 ver_b - ver_a 查詢線段樹區間 $[l, r]$ 的總和。由於是計數符合區間減法才能這樣做,否則持久化線段樹還要套別的小技巧來完成。

補充一點,雖然 KD-tree 效能是 $O(\sqrt{N} + K)$,它也支持 range searching,但必須搭配 report K 操作,那個 $O(\sqrt{N})$ 的花費是伴隨著 report 存在,因此單一筆計數詢問是無法達到 $O(\sqrt{N})$,天真的我寫下去直接 TLE。

莫隊算法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 100005;
const int MAXV = 100005;
const int MAXQ = 1000005;
const int MAXSQRTV = 512;
class Offline {
public:
struct Event {
static int PILE, belong[MAXV];
int l, r, x, y, qid;
Event(int a = 0, int b = 0, int c = 0, int d = 0, int e = 0):
l(a), r(b), x(c), y(d), qid(e) {}
bool operator<(const Event &x) const {
if (belong[l] != belong[x.l])
return l < x.l;
return r < x.r;
}
};
int A[MAXN], N, qsize;
Event event[MAXQ];
int ret[MAXQ];
void init(int N) {
this->N = N, qsize = 0;
memset(pile, 0, sizeof(pile));
memset(val_count, 0, sizeof(val_count));
val_PILE = Event::PILE = sqrt(N);
for (int i = N; i >= 1; i--)
val_belong[i] = Event::belong[i] = i / Event::PILE;
}
void q_add(int l, int r, int a, int b) {
event[qsize] = Event(l, r, a, b, qsize);
qsize++;
}
void run() {
sort(event, event+qsize);
int l = 1, r = 0;
for (int i = 0; i < qsize; i++) {
while (r < event[i].r) r++, update(A[r], 1);
while (r > event[i].r) update(A[r], -1), r--;
while (l > event[i].l) l--, update(A[l], 1);
while (l < event[i].l) update(A[l], -1), l++;
ret[event[i].qid] = query(event[i].x, event[i].y);
}
}
private:
// unrolled
int pile[MAXSQRTV], val_count[MAXV], val_belong[MAXV], val_PILE;
inline void update(int x, int val) {
val_count[x] += val;
pile[val_belong[x]] += val;
}
int query(int a, int b) {
int ret = 0;
if (val_belong[a] == val_belong[b]) {
for (int i = a; i <= b; i++)
ret += val_count[i];
return ret;
}
for (int i = val_belong[a]+1; i < val_belong[b]; i++)
ret += pile[i];
for (int i = (val_belong[a]+1)*val_PILE-1; i >= a; i--)
ret += val_count[i];
for (int i = (val_belong[b])*val_PILE; i <= b; i++)
ret += val_count[i];
return ret;
}
} off;
int Offline::Event::PILE = 16, Offline::Event::belong[MAXV];
namespace mLocalStream {
inline int readchar() {
const int N = 1048576;
static char buf[N];
static char *p = buf, *end = buf;
if(p == end) {
if((end = buf + fread(buf, 1, N, stdin)) == buf) return EOF;
p = buf;
}
return *p++;
}
inline int ReadInt(int *x) {
static char c, neg;
while((c = readchar()) < '-') {if(c == EOF) return 0;}
neg = (c == '-') ? -1 : 1;
*x = (neg == 1) ? c-'0' : 0;
while((c = readchar()) >= '0')
*x = (*x << 3) + (*x << 1) + c-'0';
*x *= neg;
return 1;
}
class Print {
public:
static const int N = 1048576;
char buf[N], *p, *end;
Print() {
p = buf, end = buf + N - 1;
}
void printInt(int x) {
static char stk[16];
int idx = 0;
stk[idx++] = '\n';
if (!x)
stk[idx++] = '0';
while (x)
stk[idx++] = x%10 + '0', x /= 10;
while (idx) {
if (p == end) {
*p = '\0';
printf("%s", buf), p = buf;
}
*p = stk[--idx], p++;
}
}
~Print() {
*p = '\0';
printf("%s", buf);
}
} bprint;
}
int main() {
int n, m, l, r, a, b;
while (mLocalStream::ReadInt(&n)) {
mLocalStream::ReadInt(&m);
off.init(n);
for (int i = 1; i <= n; i++)
mLocalStream::ReadInt(&off.A[i]);
for (int i = 0; i < m; i++) {
mLocalStream::ReadInt(&l);
mLocalStream::ReadInt(&r);
mLocalStream::ReadInt(&a);
mLocalStream::ReadInt(&b);
off.q_add(l, r, a, b);
}
off.run();
for (int i = 0; i < m; i++)
mLocalStream::bprint.printInt(off.ret[i]);
// printf("%d\n", off.ret[i]);
}
return 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
//#include <bits/stdc++.h>
#include <iostream>
#include <cstring>
#include <stdio.h>
#include <vector>
#include <algorithm>
using namespace std;
const int MAXN = 100005;
const int MAXQ = 1000005;
struct Event {
int y, qid, qtype;
Event(int a = 0, int b = 0, int c = 0):
y(a), qid(b), qtype(c) {}
};
class BIT {
public:
int v[MAXN], N;
void init(int N) {
this->N = N;
for (int i = 0; i <= N; i++)
v[i] = 0;
}
void add(int x, int val) {
while (x <= N) {
v[x] += val;
x += x&(-x);
}
}
int query(int x) {
int sum = 0;
while (x) {
sum += v[x];
x -= x&(-x);
}
return sum;
}
} bit;
namespace mLocalStream {
inline int readchar() {
const int N = 1048576;
static char buf[N];
static char *p = buf, *end = buf;
if(p == end) {
if((end = buf + fread(buf, 1, N, stdin)) == buf) return EOF;
p = buf;
}
return *p++;
}
inline int ReadInt(int *x) {
static char c, neg;
while((c = readchar()) < '-') {if(c == EOF) return 0;}
neg = (c == '-') ? -1 : 1;
*x = (neg == 1) ? c-'0' : 0;
while((c = readchar()) >= '0')
*x = (*x << 3) + (*x << 1) + c-'0';
*x *= neg;
return 1;
}
class Print {
public:
static const int N = 1048576;
char buf[N], *p, *end;
Print() {
p = buf, end = buf + N - 1;
}
void printInt(int x) {
static char stk[16];
int idx = 0;
stk[idx++] = '\n';
if (!x)
stk[idx++] = '0';
while (x)
stk[idx++] = x%10 + '0', x /= 10;
while (idx) {
if (p == end) {
*p = '\0';
printf("%s", buf), p = buf;
}
*p = stk[--idx], p++;
}
}
~Print() {
*p = '\0';
printf("%s", buf);
}
} bprint;
}
int n, m, A[MAXN], ret[MAXQ];
vector<Event> event[MAXN]; // event[pos]
int main() {
while (mLocalStream::ReadInt(&n)) {
mLocalStream::ReadInt(&m);
for (int i = 1; i <= n; i++)
mLocalStream::ReadInt(A+i), event[i].clear();
for (int i = 0; i < m; i++) {
int l, r, a, b;
mLocalStream::ReadInt(&l);
mLocalStream::ReadInt(&r);
mLocalStream::ReadInt(&a);
mLocalStream::ReadInt(&b);
// area [l, a] - [r, b] = [r, b] - [l-1, b] - [r, a-1] + [l-1, a-1]
event[r].push_back(Event(b, i, 1));
event[l-1].push_back(Event(b, i, -1));
event[r].push_back(Event(a-1, i, -1));
event[l-1].push_back(Event(a-1, i, 1));
ret[i] = 0;
}
bit.init(n);
for (int i = 1; i <= n; i++) {
bit.add(A[i], 1);
for (auto &e: event[i]) {
int val = bit.query(e.y);
ret[e.qid] += e.qtype * val;
}
}
for (int i = 0; i < m; i++)
printf("%d\n", ret[i]);
}
return 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
154
155
156
157
158
159
160
161
162
163
164
165
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <algorithm>
#include <vector>
#include <map>
#include <assert.h>
using namespace std;
const int MAXNODE = 3000000;
const int MAXN = 100005;
const int MAXQ = 100005;
class SegementTree {
public:
struct Node {
Node *lson, *rson;
int val;
} nodes[MAXNODE];
int nodesize, L, R;
Node* init(int l, int r) {
nodesize = 0;
L = l, R = r;
Node* root = build(l, r);
return root;
}
Node* newNode() {
if (nodesize >= MAXNODE)
exit(0);
return &nodes[nodesize++];
}
Node* cloneNode(Node *p) {
if (nodesize >= MAXNODE)
exit(0);
Node* u = &nodes[nodesize++];
*u = *p;
return u;
}
Node* build(int l, int r) {
if (l > r) return NULL;
Node *u = newNode();
u->lson = u->rson = NULL;
if (l == r) {
u->val = 0;
} else {
int m = (l + r)/2;
u->lson = build(l, m);
u->rson = build(m+1, r);
}
return u;
}
Node* add(Node *p, int x, int val) {
return add(p, x, val, L, R);
}
int query(Node *tp, Node *tq, int x, int y) {
return query(tp, tq, L, R, x, y);
}
private:
Node* add(Node *p, int x, int val, int l, int r) {
Node *u = cloneNode(p);
u->val += val;
if (l == r) {
return u;
} else {
int mid = (l + r)/2;
if (x <= mid)
u->lson = add(p->lson, x, val, l, mid);
else
u->rson = add(p->rson, x, val, mid+1, r);
}
return u;
}
int query(Node *tp, Node *tq, int l, int r, int x, int y) {
if (x <= l && r <= y)
return tq->val - tp->val;
int mid = (l+r)/2;
if (y <= mid)
return query(tp->lson, tq->lson, l, mid, x, y);
else if (x > mid)
return query(tp->rson, tq->rson, mid+1, r, x, y);
else
return query(tp->lson, tq->lson, l, mid, x, mid) +
query(tp->rson, tq->rson, mid+1, r, mid+1, y);
}
} segTree;
namespace mLocalStream {
inline int readchar() {
const int N = 1048576;
static char buf[N];
static char *p = buf, *end = buf;
if(p == end) {
if((end = buf + fread(buf, 1, N, stdin)) == buf) return EOF;
p = buf;
}
return *p++;
}
inline int ReadInt(int *x) {
static char c, neg;
while((c = readchar()) < '-') {if(c == EOF) return 0;}
neg = (c == '-') ? -1 : 1;
*x = (neg == 1) ? c-'0' : 0;
while((c = readchar()) >= '0')
*x = (*x << 3) + (*x << 1) + c-'0';
*x *= neg;
return 1;
}
class Print {
public:
static const int N = 1048576;
char buf[N], *p, *end;
Print() {
p = buf, end = buf + N - 1;
}
void printInt(int x) {
static char stk[16];
int idx = 0;
stk[idx++] = '\n';
if (!x)
stk[idx++] = '0';
while (x)
stk[idx++] = x%10 + '0', x /= 10;
while (idx) {
if (p == end) {
*p = '\0';
printf("%s", buf), p = buf;
}
*p = stk[--idx], p++;
}
}
~Print() {
*p = '\0';
printf("%s", buf);
}
} bprint;
}
SegementTree::Node *ver[MAXQ];
pair<int, int> A[MAXN];
int fver[MAXN];
int main() {
int n, m, x;
while (mLocalStream::ReadInt(&n)) {
mLocalStream::ReadInt(&m);
for (int i = 1; i <= n; i++)
mLocalStream::ReadInt(&x), A[i] = make_pair(x, i);
sort(A+1, A+n+1);
ver[0] = segTree.init(1, n);
for (int i = 1; i <= n; i++) {
ver[i] = segTree.add(ver[i-1], A[i].second, 1);
fver[i] = A[i].first;
}
for (int i = 0; i < m; i++) {
int l, r, a, b;
mLocalStream::ReadInt(&l);
mLocalStream::ReadInt(&r);
mLocalStream::ReadInt(&a);
mLocalStream::ReadInt(&b);
int va = (int) (lower_bound(fver+1, fver+n+1, a) - fver) - 1;
int vb = (int) (upper_bound(fver+1, fver+n+1, b) - fver) - 1;
mLocalStream::bprint.printInt(segTree.query(ver[va], ver[vb], l, r));
}
}
return 0;
}
Read More +

b414. BZOJ 3809 Gty 的二逼妹子序列

Problem

Autumn 和 Bakser 又在研究 Gty 的妹子序列了!

但他們遇到了一個難題,對於一段妹子們,他們想讓你幫忙求出這之內美麗度 $s_i \in [a, b]$ 的妹子的美麗度的種類數。為了方便,我們規定妹子們的美麗度全都在 $[1,n]$ 中。

給定一個長度為 $n$ ( $1 \le n \le 100000$ ) 的正整數序列 $s$ ($1 \le s_i \le n$),對於 $m$ ( $1 \le m \le 1000000$) 次詢問 $l, r, a, b$,每次輸出 $[s_l, \text{... },s_r]$ 中,權值 $s_i \in [a, b]$ 的權值種類數。

Sample Input

1
2
3
4
5
6
7
8
9
10
11
12
10 10
4 4 5 1 4 1 5 1 2 1
5 9 1 2
3 4 7 9
4 4 2 5
2 3 4 7
5 10 4 4
3 9 1 1
1 4 5 9
8 9 3 3
2 2 1 6
8 9 1 4

Sample Output

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

Solution

解決區間內數字在某個值域內部的種類數。

前情提要 ,如果單純問區間種類數 (獨特),可以轉換成找區間內部有多少數字大於右端點,要預處理每一個數字的下一個相同數字的位置,只要符合大於右端點的條件即為獨特。可以使用持久化線段樹來做這件事情,依序加入序列數字,貪心去保留最右側的相同數字是獨特的,藉由多版本的區間相減來完成。

現在特別要求在某個值域內部,那就沒有好方法可解,可以使用莫隊下去完成。但是莫隊算法在區間轉移 (example [l, r] to [l, r+1]) 的效能要縮小到 $O(1)$,若使用一般樹結構,莫隊算法會衰退到 $O(N^{1.5} \log N)$,還不如修改快一點 $O(1)$,詢問慢一點 $O(\sqrt{N})$,將值域使用塊狀表方式記錄,來達到 $O(N^{1.5} + Q \sqrt{N})$

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
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 100005;
const int MAXV = 100005;
const int MAXQ = 1000005;
const int MAXSQRTV = 512;
class Offline {
public:
struct Event {
static int PILE, belong[MAXV];
int l, r, x, y, qid;
Event(int a = 0, int b = 0, int c = 0, int d = 0, int e = 0):
l(a), r(b), x(c), y(d), qid(e) {}
bool operator<(const Event &x) const {
if (belong[l] != belong[x.l])
return l < x.l;
return r < x.r;
}
};
int A[MAXN], N, qsize;
Event event[MAXQ];
int ret[MAXQ];
void init(int N) {
this->N = N, qsize = 0;
memset(pile, 0, sizeof(pile));
memset(val_count, 0, sizeof(val_count));
val_PILE = Event::PILE = sqrt(N);
for (int i = N; i >= 1; i--)
val_belong[i] = Event::belong[i] = i / Event::PILE;
}
void q_add(int l, int r, int a, int b) {
event[qsize] = Event(l, r, a, b, qsize);
qsize++;
}
void run() {
sort(event, event+qsize);
int l = 1, r = 0;
for (int i = 0; i < qsize; i++) {
while (r < event[i].r) r++, update(A[r], 1);
while (r > event[i].r) update(A[r], -1), r--;
while (l > event[i].l) l--, update(A[l], 1);
while (l < event[i].l) update(A[l], -1), l++;
ret[event[i].qid] = query(event[i].x, event[i].y);
}
}
private:
// unrolled
int pile[MAXSQRTV], val_count[MAXV], val_belong[MAXV], val_PILE;
inline void update(int x, int val) {
val_count[x] += val;
if (val == -1 && val_count[x] == 0)
pile[val_belong[x]]--;
if (val == 1 && val_count[x] == 1)
pile[val_belong[x]]++;
}
int query(int a, int b) {
int ret = 0;
if (val_belong[a] == val_belong[b]) {
for (int i = a; i <= b; i++)
ret += val_count[i] > 0;
return ret;
}
for (int i = val_belong[a]+1; i < val_belong[b]; i++)
ret += pile[i];
for (int i = (val_belong[a]+1)*val_PILE-1; i >= a; i--)
ret += val_count[i] > 0;
for (int i = (val_belong[b])*val_PILE; i <= b; i++)
ret += val_count[i] > 0;
return ret;
}
} off;
int Offline::Event::PILE = 16, Offline::Event::belong[MAXV];
namespace mLocalStream {
inline int readchar() {
const int N = 1048576;
static char buf[N];
static char *p = buf, *end = buf;
if(p == end) {
if((end = buf + fread(buf, 1, N, stdin)) == buf) return EOF;
p = buf;
}
return *p++;
}
inline int ReadInt(int *x) {
static char c, neg;
while((c = readchar()) < '-') {if(c == EOF) return 0;}
neg = (c == '-') ? -1 : 1;
*x = (neg == 1) ? c-'0' : 0;
while((c = readchar()) >= '0')
*x = (*x << 3) + (*x << 1) + c-'0';
*x *= neg;
return 1;
}
class Print {
public:
static const int N = 1048576;
char buf[N], *p, *end;
Print() {
p = buf, end = buf + N - 1;
}
void printInt(int x) {
static char stk[16];
int idx = 0;
stk[idx++] = '\n';
if (!x)
stk[idx++] = '0';
while (x)
stk[idx++] = x%10 + '0', x /= 10;
while (idx) {
if (p == end) {
*p = '\0';
printf("%s", buf), p = buf;
}
*p = stk[--idx], p++;
}
}
~Print() {
*p = '\0';
printf("%s", buf);
}
} bprint;
}
int main() {
int n, m, l, r, a, b;
while (mLocalStream::ReadInt(&n)) {
mLocalStream::ReadInt(&m);
off.init(n);
for (int i = 1; i <= n; i++)
mLocalStream::ReadInt(&off.A[i]);
for (int i = 0; i < m; i++) {
mLocalStream::ReadInt(&l);
mLocalStream::ReadInt(&r);
mLocalStream::ReadInt(&a);
mLocalStream::ReadInt(&b);
off.q_add(l, r, a, b);
}
off.run();
for (int i = 0; i < m; i++)
mLocalStream::bprint.printInt(off.ret[i]);
// printf("%d\n", off.ret[i]);
}
return 0;
}
Read More +

b415. 輸出優化練習

Problem

寫題目不只會遇到算法複雜度問題,還會遇到語法上的瓶頸,了解更深入的作業系統架構、語言特性,了解每一個函數的實作方法,就能把代碼效能發揮到淋淋盡緻。當然,對於代碼轉成執行檔的最佳化技巧也是如此,接下來就來做個基礎題吧。

現在請處理一個偽隨機數計算,輸出前 $m$ 個值。

$x_{i+1} \equiv x_{i}^{2} \mod n$

有興趣的同學,可以查閱 Blum Blum Shub (BBS) Generator 相關隨機數算法。

Sample Input

1
2
3
90 141 5
52 57 5
19 129 5

Sample Output

1
2
3
90 63 21 18 42
52 25 55 4 16
19 103 31 58 10

Solution

手動轉字串,減少函數 printf() 的呼叫,一次將答案吐出來便可以達到加速,由於有一部分時間都花在取模,速度最多提升個兩到三倍。

putchar() 速度也會比 printf() 快一點,但沒有實作 buffer 來得快。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
#include <bits/stdc++.h>
namespace mLocalStream {
inline int readchar() {
const int N = 1048576;
static char buf[N];
static char *p = buf, *end = buf;
if(p == end) {
if((end = buf + fread(buf, 1, N, stdin)) == buf) return EOF;
p = buf;
}
return *p++;
}
inline int ReadInt(int *x) {
static char c, neg;
while((c = readchar()) < '-') {if(c == EOF) return 0;}
neg = (c == '-') ? -1 : 1;
*x = (neg == 1) ? c-'0' : 0;
while((c = readchar()) >= '0')
*x = (*x << 3) + (*x << 1) + c-'0';
*x *= neg;
return 1;
}
class Print {
public:
static const int N = 1048576;
char buf[N], *p, *end;
Print() {
p = buf, end = buf + N - 1;
}
void printInt(int x, char padding) {
static char stk[16];
int idx = 0;
stk[idx++] = padding;
if (!x)
stk[idx++] = '0';
while (x)
stk[idx++] = x%10 + '0', x /= 10;
while (idx) {
if (p == end) {
*p = '\0';
printf("%s", buf), p = buf;
}
*p = stk[--idx], p++;
}
}
static inline void online_printInt(int x) {
static char ch[16];
static int idx;
idx = 0;
if (x == 0) ch[++idx] = 0;
while (x > 0) ch[++idx] = x % 10, x /= 10;
while (idx)
putchar(ch[idx--]+48);
}
~Print() {
*p = '\0';
printf("%s", buf);
}
} bprint;
}
int main() {
int x0, p, n;
while (scanf("%d %d %d", &x0, &p, &n) == 3) {
for (int i = 0; i < n; i++) {
mLocalStream::bprint.printInt(x0, i == n-1 ? '\n' : ' ');
// mLocalStream::Print::online_printInt(x0);
// putchar(i == n-1 ? '\n' : ' ');
x0 = ((long long) x0 * x0)%p;
}
}
return 0;
}
Read More +

我的世界在路上 外出紀錄

前言

隔了一周,又跑中原參加一場講座。門檻壓力好可怕、好可怕,別人輕易跨過的事實成為令人怯步的記憶。人生總有那麼一兩個小事,會發現自己就是那 1% 不到的缺陷,那微乎其微基因突變雖不足以殺人,但足以折磨心理。

這一天,帶著平淡的心情出發,路程上想著一道題目,坐在陌生的圖書館中,時而發愣地等待靈感的出現,上帝並沒有給予我太多,那愚蠢又怪異舉止是在與內心世界掙扎的反應,在一旁的人群也許覺得是個怪人,人滿為患的情況下,身旁的位置總是空的,也許多慮了吧。

在講座開始前,把自己出在 zerojudge 的題目 b414, b416 寫了幾個不同解法,即使網路上有一些片段心得,有些事情還是得親自嘗試才能更加地體會。題目告一段落後,放下心去參與接下來的講座。

感謝舉辦活動的同學,願意讓我這外校生參加講座、特地向學校申請證明給我,以為這世界的人都朝著進度服從,規定之外、不在規定內事情都沒辦法做,「麻煩別人」對我而言需要 勇氣

《吹響吧!低音號》那時,我明白了

狀態

這次邀請的是旅行作家 Terry,聽到「旅行」「作家」兩個關鍵字,曾經在網路上瀏覽的文章給予的感覺,就是一個陳腔濫調的講座對吧?

事實有點不一樣,怎麼說呢,在正式開始前他分享了幾個小故事,說著他去分享講座、所遇到的人,講座中他給予別人什麼樣的經驗,而別人又是如何改變自己。在不同的國中小演講,他發現環境影響一個人的夢想,不能跟哥哥姊姊一樣聰敏的學生 A,在另一個領域演說、作文上有所才能,參與不少演說比賽的 A,卻突然不參與任何比賽,這件事情讓老師有所顧慮,父母那一句「功課要跟哥哥、姊姊一樣好,之後才不會像我一樣。」類似的話語使得 A 放棄自己的才能,而去想辦法在功課上與哥哥姊姊一樣好。

聽到這一半段,心想「這也是我放棄比賽的原因嗎?」還是 EVA 那一段話「不能逃避!不能逃避!」的掙扎,「無法做得比別人好,那就放棄吧。」回響著。

《吹響吧!低音號》不甘心地快死了

他這麼對 A 說「那你有發現你的作文演說比哥哥姊姊來得好嗎?」,學生應答「有。」接著 Terry 回了一段有趣的回答「那跟爸媽要求哥哥姊姊要在這一塊做跟你自己一樣好?」這時突然茫了,原來打破僵局這麼簡單,摸摸鼻子,暗自竊笑一番,笑著自己為什麼預期不到這樣的回答,把別人要求自己當作習慣,要求別人成了反常舉止,以後好好要求別人做得比我還好!像我如此笨拙的人,大家一定可以做得比我還好。

「演講時,跟那群小朋友說什麼大道理『要堅強、不要逃避、面對挫折沒有什麼大不了』他們聽不懂的啦,倒不如講講,踩過各種狗類型的屎分享,再去跟踩鳥屎的反應差別,還會有同學跟你分享踩牛屎的體驗呢。」

《飆速宅男》

他曾經想要單車環島,卻苦於沒有單車,將一段訊息說了出來,於是有 Terry 朋友從花蓮載了一台單車到台中給他,Terry 覺得也奇怪,那要怎麼把單車還給 Terry 朋友呢?Terry 朋友居然回答道「騎回花蓮給我吧!」聽到這一句我也傻了,住花蓮的我都清楚搭火車到西部都嫌煩,繞了半個台灣是很長的。對角線的最近距離是很短,但那段山路爬坡不是好方案。

「一生中總會有那麼幾個壞朋友,當你要去做一件事情時,在一旁煽火推你, 就好像死的是你,不是他 。也會有幾個好朋友,總會在你身旁告誡你,那樣做很危險的!不要不要啦!」- Terry

《Fate/Stay Night UBW》幹得好,你們這群壞朋友 (誤配)

大眾化的描述還真有 Terry 的風格,搭配肢體表達那該死的壞朋友,要跟聽者有所共鳴果然要這麼做吧!「說大道理我不懂,但你這麼一說我好像就明白」的那種心情,這方面我還得多學學呢。

「瘋子和勇氣只有一線之隔,而我常常是瘋子那一塊。」於是他出發了,在四、五天內穿過中央山脈到了花蓮,又經過好幾天才能下床走路,他在一年之初做了一件瘋狂的事情呢,今年的我又做了什麼創舉呢?寫寫心得、代碼、出個題目都不算什麼,咱居然 出門 聽了這場演講!我的一小步是人生的一大步。

曾經是在一家公司上班的 Terry,做在小小辦公桌的他,那時都有著一個普遍的夢想「幹掉老闆,那個位子就是我的!」這夢想是每個工程師的心言。29 歲的他第一次出國,不做幹掉老闆的夢想,出門玩玩最近火熱的 Gap Year 的舉止,看看有什麼更有趣的自己,並不是花錢出門看看別人,在別的地點用自己的努力換資源活著,拿著自己街頭賣藝的才藝,出去招搖撞騙? (笑笑就好),跟著朋友來個計劃「收集微笑」放映一段有趣的影片,看了好一陣子各國人的微笑影片,不自覺地自己也跟著笑「看著別人微笑,你的嘴角是否上揚了?」

前輩們要是過著更有希望的生活,我想我也可以過得很有希望,誰來替前輩們找到工作啊。

藉由沙發衝浪找旅宿,交換人生經驗、文化,他就這麼帶著烏克麗麗發送請求住宿,附加黑暗中華料理才藝「反正他們多數也不知道真正的中華料理,只要說我做的一定是中華料理就行了,太鹹、太油、太甜一切都沒問題的。」這樣招搖撞騙真的沒問題嗎?這時候暗自竊笑著,也許真物也不是那麼重要了,帶著自己有一點點的經驗,非專業都沒問題。

《果然我的青春戀愛物語搞錯了》世上到底有沒有真心呢

「交談怕什麼嗎?語言不好?有 Google 翻譯就行啦,這又不是考試。」想到英文考試就是一肚子的痛,Google 翻譯真的好用,翻譯品質不會是最好,能溝通就行啦!「而且沙發衝浪偏好找老人,因為老人說話慢,談話比較能聽得懂的啦!」這樣的出國技巧還真是磨練的淋淋盡致,真是無比佩服這位 計畫通 ,看似和藹可親的面容下,居然藏著一顆腹黑的心。

「這樣七十多歲的人都出國沙發衝浪」甚至在台灣 Terry 家住宿過,還隨手打著毛線。人啊,學個技能、改變第一步是困難的,但也沒有什麼不可能,那七十多歲的老騷包,還特地擔心跟著 Terry 一起拍攝影片的服裝呢,覺得衣著不好看還在那挑來挑去,身為一個教職退休的老人,要在彼此認識的小鎮做出收集微笑的活動,那還真是 害羞 呢。七十多歲的老人都在街頭做著平時只有年輕人做的事情,我是否也可以做點不一樣的?

《吹響吧!低音號》你一個人臉紅個什麼呢?

「成功不能複製,失敗可以學習。」要讓家人支持自己出國嘗試,要做些什麼?首先,先來個小旅行,逐漸地把時間拉長,讓家人明白你自己可以解決問題,對外宣稱「台灣是熱情的國家,遇到問題只要敢發問,就會有人幫忙。」對自己小孩是怎麼說「外面可怕,要小心陌生人,要注意場合。」這兩面情要怎麼讓孩子理解,到底是求助於人好、還是不求於人好?也許當前問題要先解決這個呢。

有比別人多一個機會到外處學習,回來要怎麼 分享經驗 呢?「說說自己遇到的問題,順道說說如何解決,這樣的內容才有人看啊,老媽肯定會講『看吧,就說會遇到這種情況』,分享那裏有多好玩、那裏長得如何,很少人想去聽的。」這一點到沒錯呢,覺得在 FaceBook 是一個反常的存在。不管如何,就是不喜歡看一個沒有整理過的長時間紀錄片,一定會打瞌睡。

節錄

  • 「出了遠門忘了帶錢,沒法賒帳,回家一趟也太遙遠,現場演奏怒賺菜錢,回頭結帳!爽。(高傲貌)」
  • 「旅行是一個機會,可以理解到自己的專長、發掘自己的可能性,是個機會,沒有一定。」
  • 「曾經為了搭個便車,在路旁等了五、六個小時,若你去問問女孩子通常等了多久『10 分鐘』沒錯,女孩子受到幫助的機會非常高啊,在我等了一個小時後,甚至想女裝一下,看能不能提高搭乘機率啊。」
  • 「不要說只有女孩子危險,男孩子在外搭便車也是很危險的啊,哪有什麼比較強壯什麼的,大家都是人吼。」
  • 「你看到男性提供者限定女性沙發衝浪,女性們怕不怕,若限定男性沙發衝浪,你說男性們怕不怕。怕啊。」

結論

在場聽者不多,講者仍講得相當有生動,沒有被空位的壓力屈服,話語讓我整個人都熱血起來。講著講著,好像能踏出第一步了,「對不起,看到離近的兩大門檻前,還是選擇退一步,在逃離畢業資格考前,沒辦法像用 Google 翻譯那樣地活著。」這場講座是個有趣的經驗,不僅僅是人生未來挖掘方法,也看到如何作為一個講者去描述事情的方法。

《灰色樂園》這輩子都將銘記於心

Read More +

d476. 區間查詢

Problem

一個長度為 n 的序列,支持兩種操作:

  1. 输出 $[A, B]$ 區間第 k 小的數 (從小到大排序後第 k 個)
  2. 修改第 I 個數為 W

Sample Input

1
2
3
4
5
5 3
1 2 3 4 5
Q 1 4 2
C 2 5
Q 1 4 2

Sample Output

1
2
2
3

Solution

前言

在討論用一些雜七雜八的樹類結構去跟莫隊算法搏鬥之餘,用了轉幾何方向去兜資料結構,隨後發現題目尋找的是 unique 而非計數,因此很多想法就垮台,當然出成另一道題目也是不錯。決定尋找其他想法!

開始

整體二分是什麼呢?假設操作只有修改、詢問,不包含插入、刪除,而且詢問是一個數值結果,這個數值結果可以藉由二分 + 掃描來完成,那就可以使用整體二分來離線支持。

對答案值域分治,將相關操作分治在一起,同時要保留順序性,分治過程中會不斷地累加每一個詢問的掃描數值,最後滿溢時紀錄答案。每一個操作最多在 $\log \text{MAXV}$ 層計算,因此複雜度是 $O(Q \log \text{MAXV})$

好處是可以用常數較低的結構,所以會比較快?

應用

這一道經典題有很多作法,如持久化線段樹、塊狀鏈表、歸併樹、劃分樹、 … 等。其中能支持修改的有塊狀鏈表、複雜的持久化線段樹 (主席樹)。

帶修改的區間 K 小,概略說明二分答案 $x$,然後去查找小於等於 $x$ 的數字在區間 $[l, r]$ 內出現的次數是否大於等於 $K$。然後修改元素有加入跟移除,二分答案 mid,要將加入、移除會影響的 $A[i] \le mid$,加入或移除時對該數字的索引值 $i$ 建造統計方案。

對於(位置, 值) $\left \langle i, A[i] \right \rangle$ 來說,一開始的前 $N$ 個操作是加入 $\left \langle i, A[i] \right \rangle$,二分答案時,用一個 binary indexed tree 去維護 index 的個數,也就是說,對於修改元素 $\left \langle i, A[i] \right \rangle$ 相當於在二分答案 mid 時,插入 BIT.add(i, 1),那對於區間詢問,相當於查找 BIT.query([l, r]) 如此一來就能知道區間 $[l, r]$ 在 mid 以內有幾個數字符合。

假設計數大於 k,詢問往左放,反之扣除已知 mid 以內的數量,詢問往右放,如此一來就完成了。

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
#include <bits/stdc++.h>
using namespace std;
const int MAXQ = 65536;
const int MAXN = 65536;
const int INF = 0x3f3f3f3f;
class Offline {
public:
struct Event {
int x, y, k, qtype, qid;
int calc; //
Event(int a = 0, int b = 0, int c = 0, int d = 0, int e = 0, int f = 0):
x(a), y(b), k(c), qtype(d), qid(e), calc(f) {}
};
vector<Event> event;
int N, ret[MAXQ];
void init(int N) {
this->N = N;
event.clear();
}
void addEvent(int qtype, int qid, int x, int y, int k) {
event.push_back(Event(x, y, k, qtype, qid));
}
void run() {
DC(0, event.size()-1, 0, INF);
}
private:
// buffer
int buf[MAXQ];
Event ebuf1[MAXQ], ebuf2[MAXQ];
// binary indexed tree
int bit[MAXN];
void add(int x, int val) {
while (x <= N) {
bit[x] += val;
x += (x)&(-x);
}
}
int query(int x) {
int ret = 0;
while (x) {
ret += bit[x];
x -= (x)&(-x);
}
return ret;
}
void DC(int q_l, int q_r, int val_l, int val_r) {
if (q_l > q_r) return ;
if (val_l == val_r) { // find
for (int i = q_l; i <= q_r; i++)
if (event[i].qtype == 2)
ret[event[i].qid] = val_l;
return ;
}
int mid = (val_l + val_r)/2;
for (int i = q_l; i <= q_r; i++) {
if (event[i].qtype == 0 && event[i].y <= mid)
add(event[i].x, 1);
else if (event[i].qtype == 1 && event[i].y <= mid)
add(event[i].x, -1);
else if (event[i].qtype == 2)
buf[i] = query(event[i].y) - query(event[i].x-1);
}
for (int i = q_l; i <= q_r; i++) { // resume
if (event[i].qtype == 0 && event[i].y <= mid)
add(event[i].x, -1);
else if (event[i].qtype == 1 && event[i].y <= mid)
add(event[i].x, 1);
}
int lidx = 0, ridx = 0;
for (int i = q_l; i <= q_r; i++) {
if (event[i].qtype == 0 || event[i].qtype == 1) {
if (event[i].y <= mid)
ebuf1[lidx++] = event[i];
else
ebuf2[ridx++] = event[i];
} else {
if (event[i].calc + buf[i] > event[i].k - 1)
ebuf1[lidx++] = event[i];
else {
event[i].calc += buf[i];
ebuf2[ridx++] = event[i];
}
}
}
for (int i = 0; i < lidx; i++)
event[q_l+i] = ebuf1[i];
for (int i = 0; i < ridx; i++)
event[q_l+lidx+i] = ebuf2[i];
DC(q_l, q_l+lidx-1, val_l, mid);
DC(q_l+lidx, q_r, mid+1, val_r);
}
} off;
int main() {
int n, m, x, y, k, v;
int A[65536];
char cmd[10];
while (scanf("%d %d", &n, &m) == 2) {
off.init(n);
// qtype 0:add, 1:remove, 2: query
for (int i = 1; i <= n; i++) {
scanf("%d", &A[i]);
off.addEvent(0, 0, i, A[i], 0);
}
int qid = 0;
for (int i = 0; i < m; i++) {
scanf("%s", cmd);
if (cmd[0] == 'Q') {
scanf("%d %d %d", &x, &y, &k);
off.addEvent(2, qid++, x, y, k);
} else {
scanf("%d %d", &x, &v);
off.addEvent(1, 0, x, A[x], 0);
off.addEvent(0, 0, x, v, 0);
A[x] = v;
}
}
off.run();
for (int i = 0; i < qid; i++)
printf("%d\n", off.ret[i]);
}
return 0;
}
Read More +

近代加密 小額付費

關於小額付費

另一個名稱為 微支付 (Micropayment),簡單來說提供顧客 (Consumer) - 銀行 (Broker) - 商家 (Vendor) 三方的交易。小額付費標明在交易金額小,因此交易成本也應該小,否則交易手續的計算能量就超過交易金額,更別說要扣除交易物品的成本。通常要在以下三點最小化

  • 計算資源:
    在先前提到的加密算法中,key 的 bit-length 相當長,導致計算的能量消耗大。倘若只消費 1 元,耗電成本就佔了 1% 以上,甚至更多,那麼是划不來的。
  • 儲存空間花費:
    計算每一組交易明細所需要的空間要最小,例如記錄金額需要 32-bit,那麼 n 次交易就需要 n 個 32-bit 的空間,在其他細節中還需要紀錄額外的購買來源、 … 等。
  • 管理成本:
    第三方 (通常是銀行) 要管理顧客的金額花費、商家是否能取用顧客的錢。銀行要怎麼紀錄顧客的狀態、如何驗證商家真的有跟顧客交易 … 等。

由於交易金額小,即使被破解對雙方的損失也不大。但通常破解手段需要的時間、資源往往超過交易金額,因此不太會有人去破解這個。

應用層面

網路電子報:

一份電子報可能有一些特別的標題內容,若要深入查閱內文,需要讀者付費觀看。這一部分更具有彈性,對於一份具有相當多面向的電子報而言,有些人只看幾個專欄就必須花費購買一整本的金額,有些時候是浪費的。

網路期刊購買、資料庫詢問

網路學術期刊就跟電子報的情況一樣。資料庫詢問比較特別,花費小金額來交換資料庫服務的次數。

廣告

互動式影片、廣告商為了誘拐大眾仔細觀看廣告,由廣告商付錢給觀看者。例如回答問卷回饋、機率性地抽獎回饋 … 等。

網頁閱讀

類似向大眾徵文,鼓勵分享知識。徵求到的文章,由平台供應者支付。

何時用

使用者付費,而非訂閱長時間的垃圾訊息或服務。在大多數的服務商中,會提供時間內無限使用或按照次數計費兩種方案,在大多數的情況下,時間內無限使用是對商家有利,可以預先得到使用者的投資,但使用者在後續時間內可能沒有去使用、或者沒時間去用。

不是 按照次數計費全然好 ,就如 電話費 ,打了多少通電話、講了多久,只有電信商知道,即便告訴花費相當多金額,通話明細也是由他們提供,很難證明到底花了多少,這是對使用者的不利。相較起來,小額付費比較接近投幣式的公共電話,投了多少就打多少,等到快沒有餘額時,再進行補充。

先付款給商家會對使用者不利,有可能取得不好的服務,後付款給商家會對商家不利,因為使用者可能沒有足夠的金額。正因為小額交易,適時地刷新狀態 (再投幣) 就能在之間達到平衡。

數學

由制定 RSA 那伙人 Rivest, Shamir 提供 Payword scheme 的方法。

首先,概念建立在假設不用數字儲存金額 (減少儲存空間、管理花費),而是用運算次數來知道花費金額 (用簡單運算來完成,防止能源消耗過大)。金額花費是整數,由最小單位 1 元構成,如果是在通貨膨脹的國家,最小單位可能不一樣?

那麼可以建立一個儲值金額 n 元的串列如下。

$x_0 \leftarrow x_1 \leftarrow x_2 \leftarrow \text{ ... } \leftarrow x_{n-1} \leftarrow x_n$

由使用者拿一個$x_n$ 初始化,接著利用 hash$x_i = h(x_{i+1})$ 得到每一個硬幣$x_i$。用數學表示遞迴公式$x_i = h^{n-i}(x_n)$

當顧客跟商家交易時,簽一份簽章 (例如用 RSA 或者是 DSA 等方式) 給商家。簽章內容為

$\text{Sign}_C(\text{Merchant-ID} || x_0 || \text{Cert})$

其中 $\text{Cert}$ 是由第三方信任銀行簽署給用戶的內容 (類似銀行帳戶的確認)。最後發送給商家資訊為

$\text{Sign}_C(\text{Merchant-ID} || x_0 || \text{Cert}), \text{Merchant-ID}, x_0, \text{Cert}$

由商人去驗證 (檢查簽證內容、拿對方的 public key 打開,再去拿銀行的 public key 驗證 $\text{Cert}$ 是否正確) 是否是可信任的顧客或者是顧客是否存在。

接下來交易採用最笨的方案,每一次交易 1 元,交付 i 元時,顧客就告知$x_i$ 值給商家,這裡顧客得到硬幣方法為重新計算$x_i = h^{n-i}(x_n)$,可以不事先用記憶體儲存$[x_0, x_n]$,保留 $n$$x_n$ 即可。

之後商家拿著 i 元$x_i$$\text{Sign}_C(\text{Merchant-ID} || x_0 || \text{Cert})$ 的資訊給銀行,銀行計算 $i$ 次 hash$x_0 = h^i(x_i)$ 查看是否正確,若檢驗沒問題,則銀行從客戶存簿撥款給商家。

為什麼可行

運算過程中使用 hash,hash 提供不可逆的操作,因此商家無法有更多金額的索取,假設從顧客手中得到$x_i$,無法計算出$x_{i+1}$ 究竟是什麼,具有一定安全性。而顧客必須由銀行索取簽證,來表示當前的狀態是合法的,顧客沒辦法竄改自己的狀態,使得自己有更多的金錢,銀行那裏會有最新一組的簽章在?應該是吧,銀行會在商家發送驗證資訊時發生異狀檢查顧客是否竄改。

數學之美。

Read More +