【2020牛客多校】2020牛客暑期多校訓練營(第二場)H-Happy Triangle——動態開點線段樹+STL+區間化點
在WA了好多發之後,終於找到了我不小心寫錯的bug……我是SB
我的寫法與網路上很多人的差異較大,但是個人覺得比其他人的更容易理解
第一次寫動態開點的線段樹,直接稍微改動了一下原本自己習慣的線段樹板子,所以可能與其他板子不同。
同時因為是改了線段樹的板子,所以反而更容易看懂。
其次就是個人感覺我的寫法比題解要簡單很多,而且碼量很小
題意
對於一個可重複集合,進行Q次操作。集合起始的時候為空,操作型別如下
- 往集合中加入一個元素
- 從集合中刪除一個元素(保證其存在)
- 給定一個元素 \(x\) ,問集合中是否存在另外兩個元素\(a, b\)(允許值相同但是不允許元素相同),使得\(a, b, x\)
分析
分析三角形公式
首先根據公式\(a + b > c\) 轉為 \(c - b<a\) (假定\(a \leq b \leq c\))
那麼我們可以得到,下面的結論:
假定存在 \(a \leq b\) 滿足 \(a, b, c\) 三邊能夠組成三角形,那麼對於 \(a \leq a' \leq b\) 必定存在 \(a', b, c\) 可以組成三角形(由 \(c - b<a \leq a'\) 證得)
那麼我們可以指定如下規定:
- 對於輸入的 \(x\) ,我們找到兩條長度分別為 \(a, b\)
即 \(a, b\) 在整個集合排序後,在陣列中的下標差為 \(1\)
接下來考慮如何找到 \(a, b\)。題解中提到類似分類討論,但是我覺得沒有必要。我們僅考慮通過 \(a, b\) 的運算後的結果與 \(x\) 來比較,最終得到我們的結果是否符合。
根據 \(a, b, x\) 的大小關係討論三種情況:(前提 \(a \leq b\))
- \(x\) 為最大值時,我們只需要保證 \(a + b > x\)
- \(a \leq x \leq b\) 時,我們需要保證 \(a + x > b\) ,轉換後得到 \(b - a < x\)
- \(x \leq a \leq b\) 時,我們需要保證 \(x + a > b\) ,轉換後得到 \(b - a < x\)
總結:我們只需要保證我們選出來的\(a, b\) 保證 \(a + b > x \space and \space b - a < x\)
由於 \(a \leq b\) 所以\(b \geq x / 2\)(請先記住這個結論,將會在之後用到)
接下來是解決 \(a + b\) 和 \(b - a\) 的資料儲存和更新問題(由於詢問是線上詢問,而 \(a, b\) 相鄰,所以隨著插入新的資料,這兩個值都會發生變化)。
對於 \(a + b\) 的處理:
我們將所有當前在集合中的資料進行排序,可以使用 multiset
來實現,但是我個人不太喜歡 multi
的資料結構,所以我選擇了 map
,first
儲存資料的值,second
儲存了資料重複的個數。從此開始,我們暫時不討論重複值的情況。
對於排序好的資料,我們可以通過二分數值來得到 \(x / 2\) 在陣列中的哪個位置。由於 \(a \leq b\) ,所以只有兩種可能
- \(a < x/2 \space and \space b > x / 2\)
- \(a, b \geq x / 2\)
後者很好解決,只需要取值的時候,陣列下標大於 \(x/2\) 所在的下標位置即可
而前者因為 \(a, b\) 相鄰,所以我們可以使用 upper_bound
輕鬆解決(b = *map.upper_bound(x / 2), a = *(map.upper_bound(x / 2) - 1)
)
至此,在保證資料有序的情況下,我們已經第一步縮小了資料範圍,得到了一個數組下標範圍 [map.upper_bound(x / 2), map.end()]
。注意,這裡的右區間始終為最大值(\(INF\))
對於 \(b - a\) 的處理
由於求算 \(b - a\) 的過程本身需要排序,而上面對 \(a + b\) 的處理的時候已經排完序了。所以我們能夠較快的得到 \(b - a\) 的值( \(a, b\) 相鄰)但是此時的更新的操作過於複雜,而且我們並不需要知道哪個區間的值能夠滿足條件(即小於 \(x\) ),我們可以只需要知道我們已經縮小後的區間內,是否存在 \(a, b\) 滿足 \(b - a < x\),即 \(min(b - a) < x\)。
區間最小值,單點更新,此時我們想到了線段樹(主要是我不知道有沒有動態樹狀陣列這個感覺不太可能存在的東西,於是就寫了線段樹,實際上需要將線段樹的空間動態化,不然空間會爆炸)。
對於整個集合,假如有 \(n\) 個不同的元素,則只會產生 \(n - 1\) 個不同的插值(由於 \(a, b\) 相鄰,每個元素只會產生一個,假定最後一個元素不產生)
那麼我們建立一棵長度為 \(1e9\) 的線段樹,對於每個不同的值(x),將其產生的差值儲存在節點 \(x -x\) 下,其他沒有值的節點,則保持 \(INF\)
舉一個例子,假如我們有如下值在集合中
1, 3, 4, 10, 123, 423
則此時得到的差值為
2, 1, 6, 113, 300
則我們對如下陣列a
建立線段樹
a[1] = 2;
a[3] = 1;
a[4] = 6;
a[10] = 113;
a[123] = 300;
由於輸入的 \(1 \leq x \leq 1e9\),所以我們開不起這麼大的線段樹,而實際上最多隻會有 \(1e5\) 個葉子節點,所以線段樹最多的節點個數為 \(1e5 \space lg 1e5 < 1e7\),所以只需要準備 \(1e7\) 個節點,然後動態開點即可滿足整個線段樹的需要。
至於這裡為什麼選擇將每個差值產生的較小者(即 \(a\) )作為下標的儲存位置。由於之後會遇到前面 \(a + b\) 得到的區間恰好從 \(a, b\) 中間穿過,如果儲存的是在 \(b\) 下,則會出現 \(a + b < x\) 但是仍然被選出來作為 \(min(b - a)\)。
接下來是線段樹的更新操作。
插入
由於值儲存在較小值處,所以需要更新較小值的值,和當前新插入的節點的下的值
刪除
由於刪除操作難以實現,不如直接把被刪除的點的值設定為 \(INF\),以及,被刪掉的點前面一個點的值需要更新
注意一下各種邊界情況。
查詢的操作
首先從已經排序好的陣列中,得到 \(x / 2\) 所在陣列中的區間,然後拿著這個區間去找線段樹,詢問區間最小值,將最小值與 \(x\) 比較,如果最小值比 \(x\) 小,則輸出 Yes
,否則輸出 No
處理重複的資料
這裡就相當簡單了,對於相同的資料,只需要保證 \(a + a > x\) 即可滿足 \(a, a, x\) 能夠組成三角形。我選擇再建立了一個 set
將所有滿足個數大於等於 \(2\) 的值均儲存在陣列中,然後去尋找是否存在 set
中是否存在 \(a\) 滿足 \(a > x / 2\),則可以得到解
AC code
#include <bits/stdc++.h>
using namespace std;
#define MAXN 8000000
const int maxn = 1e9;
struct SegTree {
int tot;
int sub[MAXN]; // 儲存了差值
int lson[MAXN], rson[MAXN];
void init() {
for (int i = 0; i < MAXN; ++i)
sub[i] = INT_MAX;
memset(lson, 0xff, sizeof(int) * MAXN);
memset(rson, 0xff, sizeof(int) * MAXN);
tot = 1;
}
inline void up(int cur) {
if (lson[cur] == -1 && rson[cur] == -1) sub[cur] = INT_MAX;
else if (lson[cur] == -1) sub[cur] = sub[rson[cur]];
else if (rson[cur] == -1) sub[cur] = sub[lson[cur]];
else sub[cur] = min(sub[lson[cur]], sub[rson[cur]]);
}
inline int getLson(int cur) {
assert(tot < MAXN);
if (lson[cur] == -1)
lson[cur] = tot++;
return lson[cur];
}
inline int getRson(int cur) {
assert(tot < MAXN);
if (rson[cur] == -1)
rson[cur] = tot++;
return rson[cur];
}
void update(int x, int value, int cur = 0, int l = 1, int r = maxn) {
if (x == l && l == r) {
sub[cur] = value;
return;
}
int mid = (l + r) >> 1u;
if (x <= mid) {
update(x, value, getLson(cur), l, mid);
} else {
update(x, value, getRson(cur), mid + 1, r);
}
up(cur);
}
int query(int x, int y, int cur = 0, int l = 1, int r = maxn) {
if (x == l && y == r) return sub[cur];
int mid = (l + r) >> 1u;
if (y <= mid) {
return query(x, y, getLson(cur), l, mid);
} else if (x > mid) {
return query(x, y, getRson(cur), mid + 1, r);
} else {
return min(query(x, mid, getLson(cur), l, mid),
query(mid + 1, y, getRson(cur), mid + 1, r));
}
}
} segTree;
void solve() {
int q;
cin >> q;
segTree.init();
map<int, int> pool; // 當前集合中的資料
set<int> multi; // 用於處理重複資料
for (int i = 0; i < q; ++i) {
int op, x;
cin >> op >> x;
switch (op) {
case 1: {
auto iter = pool.find(x);
if (iter != pool.end()) {
iter->second++;
if (iter->second == 2)
multi.insert(x);
} else {
pool.insert({x, 1});
auto cur = pool.find(x);
auto lower = cur, up = cur;
up++;
if (lower != pool.begin()) {
lower--;
segTree.update(lower->first, x - lower->first);
}
if (up != pool.end()) {
segTree.update(x, up->first - x);
}
}
}
break;
case 2: {
auto cur = pool.find(x);
if (cur == pool.end()) break;
cur->second--;
if (cur->second == 1) {
multi.erase(x);
} else if (cur->second == 0) {
auto lower = cur, up = cur;
up++;
segTree.update(x, INT_MAX);
if (lower != pool.begin()) {
lower--;
if (up != pool.end())
segTree.update(lower->first, up->first - lower->first);
else
segTree.update(lower->first, INT_MAX);
}
pool.erase(cur);
}
}
break;
case 3: {
auto iter = pool.upper_bound(x / 2);
if (iter == pool.end()) { // 沒有值比 x / 2 更大了,則不存在 a + b > x 了
cout << "No" << endl;
break;
}
auto lower = iter;
if (lower != pool.begin()) {
lower--;
if (lower->first + iter->first <= x) {
lower++;
}
}
auto mu = multi.lower_bound(iter->first);
if (mu != multi.end()) {
cout << "Yes" << endl;
} else {
int res = segTree.query(lower->first, maxn);
if (res < x)
cout << "Yes" << endl;
else
cout << "No" << endl;
}
}
break;
}
}
}
signed main() {
ios_base::sync_with_stdio(false);
cin.tie(nullptr);
cout.tie(nullptr);
#ifdef ACM_LOCAL
freopen("in.txt", "r", stdin);
freopen("out.txt", "w", stdout);
int test_index_for_debug = 1;
char acm_local_for_debug;
while (cin >> acm_local_for_debug) {
if (acm_local_for_debug == '$') exit(0);
cin.putback(acm_local_for_debug);
if (test_index_for_debug > 20) {
throw runtime_error("Check the stdin!!!");
}
auto start_clock_for_debug = clock();
solve();
auto end_clock_for_debug = clock();
cout << "Test " << test_index_for_debug << " successful" << endl;
cerr << "Test " << test_index_for_debug++ << " Run Time: "
<< double(end_clock_for_debug - start_clock_for_debug) / CLOCKS_PER_SEC << "s" << endl;
cout << "--------------------------------------------------" << endl;
}
#else
solve();
#endif
return 0;
}