2021 高階競程 Contest #2 - 題解
- Task Provider: leo
- Task Setter: leo
首殺 team21_28 (00:28)
解法說明
「保證任兩個不同的基地台都有辦法互相傳輸資料,
且兩基地台之間傳輸資料的路徑唯一。」
題目敘述最後的這句話是關鍵,
因為這句話代表了一張圖是聯通的,
且兩點之間的路徑唯一。
由這兩點可以推導出題目給定的是一棵樹,
那麼樹的著色問題就簡單了。
當節點只有一個時,僅需一種顏色即可;
如果大於一個節點也只需兩種顏色。
可以看成你將樹隨便選一個根,
第一層塗成顏色 ,第二層塗成顏色 ,第三層塗成顏色 ,…
即可將所有節點著色完畢,且相鄰節點顏色不同。
P.S. 題目測資的輸入中, 其實都是 ,題目中 的範圍算是一種誤導
標準程式
- Task Provider: ys
- Task Setter: ys
首殺 team21_09 (00:42)
解法說明
解法 1
觀察當 長度為 的情況 (因為足夠小 以便摸索解法)
如
不失一般性的設
- 若 則
- 若 則
- 若 則
所以要將 落在 3. 的條件下才使 盡量小
再細看
從 遞增至
會發現 與 絕對差越來越大,而與 絕對差越來越小
在這之中 與 的兩個絕對差會逐步至一個最小對,接著再增大
則從這對中找最大的那個
反之從 遞減至 也一樣
這貌似是個類似二次函數的凸函數 () (只有一個局部極值)
若是能證明任意長度的 都有此函數特性,那麼可以用三分搜解決
不用管不與 相鄰的絕對差,
因為不與 相鄰的絕對差之中的最大值 永遠都不會變動,
也就是說若函數擁有理想上的特性,那麼 在遞減時也只會降到 。
而與 相鄰的絕對差,也只需要關心最大值 與最小值
因為若存在介於 的數 使得
則若 得 (因 是最小值),但 矛盾
或若 得 (因 是最大值),但 矛盾
帶絕對值,所以要拆兩狀況論證
也就是說,任意長度的 可類比為長度 的情況
解法 2
根據解法 1 的結論,只需找出與 相鄰數字中的最大值 與最小值
則 ,可根據該式直接計算出 為多少
題目要求 的最小值,則該值為 與 的交點
解法 1 沒有給出 應為多少,得用找的
解法 3
根據解法 1 的結論, 對 的函數是類似二次函數的凸函數
也就是說,該函數的斜率是單調的,所以可以用二分搜來找
實作上,利用解法 1 的程式 double m(double k)
透過 m(k+0.01) - m(k) > 0
就能知道在 k
上 m(k)
的變化是否為遞增
此變化量也可以看做是該函數在 上的微分
標準程式
解法 1
解法 2
解法 3
- Task Provider: D4nnyLee
- Task Setter: D4nnyLee
首殺 team21_12 (02:30)
解法說明
這題可以用二分搜來解。
首先我們先定義 代表所有合併前的陣列, 代表第 個陣列,
然後再寫一個函式 ,使得 是所有 的陣列中,小於 的元素數量。
因為每個 都有規律,所以要算出 其實不難。
有了 之後,可以發現這題的答案有單調性,
因為 一定成立。
假設最後的答案為 ,則 必定成立。
為什麼會有小於 的情況呢?
用例子來做解釋:
假設 為所有區間合併並排序完後的數列,令 ,
現在索引值為 的元素的值是 ,但是所有小於 的元素只有 個。
題目要找的就是一個最大的整數 ,使得 。
標準程式
因為數字的範圍很大,用 int
很容易遇到整數溢位(Integer Overflow)的問題,
所以下面實作都使用 long long
。
- Task Provider: alan8585
- Task Setter: leo
首殺 – (-:-)
解法說明
可以把每個格子都當成一個獨立節點,
若兩個節點相鄰的話可以合併成一個集合,
講到這邊會想到「併查集」這個資料結構。
因為併查集支援查詢與合併,
因此可以將所有輸入都讀進來,
並且反向操作,題目變成,
「給定一張最終的圖,每次操作將一個格子塗成白色,
請問每次操作後會有多少聯通塊,以及最大聯通塊大小為何」。
只要每次塗成白色,就將該節點與周圍白色的節點合併,
並且在合併過程取最大值即可。
時間複雜度為輸入的 ,
加上每次合併查詢操作的 ,
乘上操作次數 ,總複雜度為 。
標準程式
#include <iostream>
#include <utility>
#include <bitset>
#define F first
#define S second
using namespace std;
const int MAXN = 3001, dx[4] = {1, 0, -1, 0}, dy[4] = {0, 1, 0, -1};
bitset<MAXN> a[MAXN];
int n, m, boss[MAXN * MAXN], sz[MAXN * MAXN], mx, cnt;
pair<int, int> que[200000], ans[200000];
int find(int x){
if(boss[x] == x)
return x;
return boss[x] = find(boss[x]);
}
void combine(int x, int y){
x = find(x), y = find(y);
if(x == y)
return;
sz[x] += sz[y];
boss[y] = x;
cnt--;
mx = max(mx, sz[x]);
}
bool check(int x, int y){
return x > 0 && x <= n && y > 0 && y <= m && !a[x][y];
}
int trans(int x, int y){
return x * m + y;
}
int main() {
ios::sync_with_stdio(0);
cin.tie(0);
int q;
cin >> n >> m >> q;
for(int i = 1; i <= n; i++)
for(int j = 1, x; j <= m; j++)
cin >> x, a[i][j] = x, boss[trans(i, j)] = trans(i, j), sz[trans(i, j)] = 1;
for(int i = 0; i < q; i++){
cin >> que[i].F >> que[i].S;
a[que[i].F][que[i].S] = 1;
}
for(int i = 1; i <= n; i++)
for(int j = 1; j <= m; j++)
if(!a[i][j]){
cnt++, mx = max(mx, 1);
for(int k = 0; k < 2; k++){
int nx = i + dx[k], ny = j + dy[k];
if(check(nx, ny))
combine(trans(i, j), trans(nx, ny));
}
}
for(int i = q - 1; i >= 0; i--){
ans[i] = make_pair(cnt, mx);
int x = que[i].F, y = que[i].S;
a[x][y] = 0;
cnt++;
for(int j = 0; j < 4; j++){
int nx = x + dx[j], ny = y + dy[j];
if(check(nx, ny))
combine(trans(x, y), trans(nx, ny));
}
mx = max(mx, sz[trans(x, y)]);
}
for(int i = 0; i < q; i++)
cout << ans[i].F << " " << ans[i].S << "\n";
}
- Task Provider: NHDK Moon Festival Ten Point Round 2021
- Task Setter: raiso
首殺 team21_09 (02:40)
解法說明
首先,本題目是在找 區間和 的個數,如果直接枚舉 與 ,時間複雜度為 。
使用前綴和加速計算區間和,時間複雜度是 。
考慮到一個問題,區間和就是兩個前綴和相減,也就是尋找兩個前綴和,前面的前綴和大於後面的前綴和時,此對應的區間和就是 ,當我們將問題思考到這邊的時候,就完成了這題的要求。
前綴和的逆序數對數量就是解。
標準程式
解法1
解法2