GYOI Beginning Contest #1 Div.3
Welcome to GBC Round 1。
題目順序按難度排序。
Problem Contents
來自 XU 的評價:
T1 需要思考一下
T2 有點水
T3 直接暴力
T4 ?
Problem Hint List
T1 Hint
到底要怎樣才需要交換呢?
T2 Hint
我們設 $g(i, j)$ 表示結點 $i$ 到他的第 $2^j$ 個父親的 $\gcd$。
T3 Hint
需要用什麼演算法來求路徑的數量?
T4 Hint
T1 Treap
讓 $0$ 在前 $1$ 在後的方案,即當 $1$ 在 $0$ 前時則必須使用一次交換,其餘 $0$ 或 $1$ 可以跟著這次交換來交換。所以答案就是 10
出現的次數。
當然也可以求除了開頭的 0
連通塊個數。
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 5;
int a[N];
int n;
int main(){
cin>>n;
for(int i = 1;i <= n;i++){
cin>>a[i];
}
int ans = 0;
for(int i = 1;i < n;i++){
if(a[i] == 1 && a[i+1] == 0) ans++;
}
cout<<ans<<"\n";
return 0;
}
T2 Search
倍增建樹
最大公因數會隨著數的增加,非嚴格減小。所以要使 $l \to r$ 最短。
這題實在有點簡單,只是程式碼量高。由題意的移動可以自然聯想到完全二叉樹,於是就可以建樹。
我們現在要求的是 $u \to v$ 的 $\gcd$,還要路徑最短,就可以使用 lca 處理。$\gcd(u \to v) = \gcd((u \to \text{lca}),(\text{lca} \to v))$
$g(i, j)$ 表示結點 $i$ 到他的第 $2^j$ 個父親的 $\gcd$,所以 $g(i,j)=\gcd(g(i,j-1),g(f(i,j-1),j-1))$,即 $2^{j-1}$ 個父親的 $\gcd$ 與第 $2^{j-1}$ 個父親到他的第 $2^{j-1}$ 個父親的 $\gcd$,就是 $u$ 到他的第 $2^j$ 個父親的 $\gcd$ 了。
所以答案就是 $\gcd(g(l, \text{lca}), g(v, \text{lca}))$。
簡單做法
適用於不想建樹的朋友。
我們發現 $l \to 1$ 需要經過的結點就是 $l$ 一直 $/2$ 所經過的節點,要求這個只需要 $\log l$ 的時間複雜度,自然可以透過本題。最後基於 $\text{lca}$ 的父親也一定是 $l, r$ 的父親,所以只需要從根 $1$ 一直往下搜尋知道出現分支,那上一個就是 $\text{lca}$。
最後在路徑上取 $\gcd$ 即可
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const int N = 1e5 + 5;
ll a[N], n, q;
int stk1[N], stk2[N], tot1, tot2;
int main(){
ios::sync_with_stdio(0);
cin.tie(0);
cin>>n>>q;
assert(1 <= n && 1 <= q && n <= 100000 && q <= 100000);
for(int i = 1;i <= n;i++) cin>>a[i];
while(q--){
int l, r;
cin>>l>>r;
assert(1 <= l && 1 <= r && l <= n && r <= n);
tot1 = tot2 = 0;
while(l){
stk1[++tot1] = l;
l /= 2;
}
while(r){
stk2[++tot2] = r;
r /= 2;
}
int p = 1;
while(stk1[p] == stk2[p]){
if(p == min(tot1, tot2)) break;
p++;
}
ll ans = a[ stk1[p-1] ];
for(int i = p;i <= tot1;i++){
ans = __gcd(ans, a[ stk1[i] ]);
}
for(int i = p;i <= tot2;i++){
ans = __gcd(ans, a[ stk2[i] ]);
}
cout<<ans<<"\n";
}
return 0;
}
T3 DIJ
一道比較明顯的 dfs,若不給樣例 2,則這題難度在黃。
由樣例 2 可得,若圖自環,則有無數解。
若有 $d(u)$ 表示 $u \to n$ 的方案數,就可以記憶化搜尋,即 $d(u) = \sum d(deg(u))$,即出邊的 $d$ 和。若有自環卻不能聯通 $n$,則輸出 $0$,所以要特判。
答案記得開 long long。
時間複雜度 $O(n + m)$。
#include <bits/stdc++.h>
#define int long long
using namespace std;
using ll = long long;
const int N = 1e6 + 5;
const ll MOD = 702649740970;
int n, m;
ll d[N], vis[N];
vector<int> g[N];
bool iszh = 0;
ll dfs(int u){// 返回到n的方案數
ll sum = 0;
bool f = 0;
for(auto v : g[u]){
if(d[v] != -1){
sum += d[v] % MOD;
sum %= MOD;
continue;
}
if(vis[v]){
f = 1;
continue;// 有自環
}
vis[v] = 1;
sum += dfs(v) % MOD;
sum %= MOD;
}
d[u] = sum % MOD;
if(f && sum) iszh = 1;
return sum;
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0);
cin>>n>>m;
for(int i = 1;i <= m;i++){
int u, v;
cin>>u>>v;
g[u].push_back(v);
}
memset(d, -1, sizeof d);
d[n] = 1;
vis[1] = 1;
dfs(1);
if(iszh) cout<<-1;
else cout<<d[1];
return 0;
}
T4 Plus
性質
- 總是可以在 $b$ 的左、右端 $+1$,如 $123 \to 124$ 或 $123 \to 321 \to 322 \to 223$
- 可以不花代價給左、右端增加數字 $0$,如 $123 \to 0123$ 或 $123 \to 321 \to 0321 \to 1230$
- 無論如何,我們不能單獨修改 $b$ 內部的元素,只能透過進位。如要使 $123 \to 133$,只能 $+10$。
思路
在性質二下,使用性質一,可以讓 $b$ 的左端點增加任意數,所以除了 $b$ 以外的數,要使其它元素分別等於 $a$ 中的數,代價就是 $\sum_{j=1}^{n}a_j, ~ j \notin {b \text{所在的下標}}$。而 $b$ 內部的代價基本就是 $a$ 所對應的序列與 $b$ 的差。
再考慮 $b$ 內部的數,其必然匹配 $a$ 長度為 $m$ 的子序列。設 $a$ 中,下標為 $i \sim j$ 的子序列所組成數的為 $[a_i, a_j]$。若 $b$ 匹配 $[a_i, a_{i+m}]$,則:
要使 $b \to [a_i, a_{i+m}]$,還需要幾個特性:
- 當 $b_1 + 1$ 時,應當往右邊進位而不是往左。
- $10^i > \sum_{j = 0}^{i-1} (10^j \times 9)$,即在貪心最小的時候,要儘量往位數小的做。
考慮到只有兩邊能動,所以答案要使得 $b$ 從兩端出發的子序列分別與 $a$ 所對應的子序列形成的差最小,自然要讓兩個子序列位數分別最少,即以中位數為中間,把左右兩邊分成兩個序列,每個序列與 $a$ 所對應的序列的差就是代價的最小值。
於是代價就是左半子序列的倒序 $+$ 右半子序列。其中子序列指差值的子序列形成的數。
當然,這種情況僅限於 $a$ 的左半子序列的倒序 $\ge$ $b$ 的左半子序列的倒序,$a$ 的右半子序列 $\ge$ $b$ 的右半子序列。
分類
再考慮借位。我們發現,若有兩個整數 $x, y$,其數位都為 $s$,則 $x-y$ 的借位最多為 $(x-y + 10^{s}) \bmod 10^{s} < 10^{s+1}$,所以無論借位與否,中位數永遠是最優答案。
很明顯,這題推到這就成了一個大分類的題目。
設 $a$ 的左半子序列的倒序 $[a_i, a_{i + \frac{m}{2}}]$ 為 $a_l$,$a$ 的右半子序列的正序 $[a_{i + \frac{m}{2} + 1}, a_{i+m}]$ 為 $a_r$。$b$ 同理,將 $i$ 替換成 $1$。
若 $a_l \ge b_l, a_r \ge b_r$,則答案就是 $a_l - b_l + a_r - b_r$,前面已經提到過了。
若 $a_l \ge b_l, a_r < b_r$,則發現,右邊的需要透過借位實現減法,而左邊不用,所以我們需要使 $b_l$ 的首項 $+1$,於是又有了判斷
若 $a_l \ge b_l$ 則左邊仍然能夠被減,所以答案就是 $a_l - b_l + a_r - b_r$
否則,這個方式無解。
若 $a_l < b_l, a_r \ge b_r$ 則發現,左邊的需要透過借位實現操作,所以需要 $b_r$ 的首項 $+1$,
若 $a_r \ge b_r$,則左邊仍然能夠被相減,所以答案就是 $a_r - b_r + a_l - b_l$
否則,這個方式無解。
若 $a_r < b_r, a_l < b_l$,則這個中間值無解。
現在,我們做完了中間值的大分類,但答案肯定不止一箇中間值。我們將 $a_l, a_r$ 定義為以 $\text{mid}$ 為中間值的左半子序列和右半子序列。$b_l, b_r$ 同理,中間值為 $\text{mid} - i$,但為方便理解,統一寫作 $\text{mid}$。
維護最小中間值
到了這裡,我們要滿足答案的 $\text{mid}$ 離 $i + \frac{m}{2}$ 最短,還要讓 $\text{mid}$ 有解,需要在 $O(\log)$ 算出。
在以往的思路內,我們常用 $b \to a_{str}$,不妨換個思路,讓 $a_{str} \to b$。自然要使得其中的最高位有解,於是可以預處理 $a$ 中每個 $\text{mid}$ 的左右最高值,將其排序,可以二分找到最優解。
實際上,現在還有一點不明確的地方,那就是大小判斷。當左邊要進位,$b$ 左邊的最高位可能和 $a$ 左邊的最高位相等,就不能 $\theta(1)$ 求出大小了。既然資料保證了每一位互不相同,那就有它們的下一位互不相同,自然可以解出 $a_l,b_l$ 的大小關係。
在很多情況下,答案總是會無法透過合法的中間值算出答案,所以這時要重新讓 $b \to a_{str}$,並計算最差進位下的答案最小值。需要注意,此時不能無腦取模取最小:因為模數的存在,又要使答案最小,只能透過性質 5 來貪心。而模數的處理只需要從答案的左右段分別取 $10^9$ 個數位形成的數再取模。