--- title: HNaOI Day 2 --- # Solution HNaOI Day 2 ## Problem 1 : Tán gái 505 Trước hết, ta sẽ biến đổi biểu thức $S$ như sau : $$ S = \sum_{i = 1} ^ {n - 1} \frac{gcd(a_i, a_{i + 1})}{a_i \times a_{i + 1}} = \sum_{i = 1} ^ {n - 1} \frac{1}{lcm(a_i, a_{i + 1})}$$ Từ đó ta sẽ có **bất đẳng thức** sau : $$ S = \sum_{i = 1} ^ {n - 1} \frac{1}{lcm(a_i, a_{i + 1})} \le 1 - \frac{1}{2 ^ {n - 1}}$$. Ta sẽ chứng minh bất đẳng thức này bằng phương pháp **quy nạp :** 1. Xét $n = 2$, khi đó ta có : $$ \\ S \le \frac{1}{lcm(1, 2)} = \frac{1}{2} = 1 - \frac{1}{2 ^ {2 - 1}}(tm)\\ $$ 2. Giải sử $n$ đúng với $k(k \ge 2)$, ta sẽ đi chứng minh $n$ đúng với $k + 1$ . * Xét $a_{k + 1} \ge 2 ^ k$ $\Longrightarrow lcm(a_k, a_{k + 1}) \ge a_{k + 1} \ge 2 ^ k$. Theo giả thiết quy nạp, ta có : $$ \frac{1}{lcm(a_1, a_2)} + \frac{1}{lcm(a_2, a_3)} + ... + \frac{1}{lcm(a_k, a_{k + 1})} \le 1 - \frac{1}{2 ^ {k - 1}} + \frac{1}{2 ^ k} = 1 - \frac{1}{2 ^ k} (tm)$$ * Xét $a_{k + 1} < 2 ^ k$, khi đó ta có : $$ \frac{1}{lcm(a_i, a_{i + 1})} = \frac{gcd(a_i, a_{i + 1})}{a_i a_{i + 1}} \le \frac{a_{i + 1} - a_i}{a_i a_{i + 1}} = \frac{1}{a_i} - \frac{1}{a_{i + 1}}$$ Cộng các vế của bất đẳng thức như trên với $i$ từ $1$ đến $k$, ta sẽ được : $$ \frac{1}{lcm(a_1, a_2)} + \frac{1}{lcm(a_2, a_3)} + ... + \frac{1}{lcm(a_k, a_{k + 1})} \le \frac{1}{a_1} - \frac{1}{a_{k + 1}} \le 1 - \frac{1}{2 ^ k} (tm)$$ Theo phương pháp quy nạp, ta được điều phải chứng minh. Dấu $" = "$ xảy ra khi $a_i = 2 ^ {i - 1}$ $\forall i \in [1, n]$. ```c++ #include <bits/stdc++.h> using namespace std; int main() { ios_base::sync_with_stdio(false); cin.tie(NULL); int n; cin >> n; long double res = 1; cout << fixed << setprecision(0); for (int i = 1; i <= n; ++i) { cout << res << " "; res += res; } return 0; } ``` ## Problem 2 : Tán gái 606 Lời giải sẽ được cập nhật trong thời gian sớm nhất ### Subtask 1 : Ta chỉ cần **dijkstra** từ đỉnh 1 đến các đỉnh còn lại ~~(quá ez với subtask này)~~. ### Subtask 2 : Lúc này, ta không thể **dijkstra** một cách bình thường được. Thay vào đó ta có cách tiếp cận sau : Gọi $dp[u][i]$ và thời gian nhanh nhất đi từ $1$ đến đỉnh $u$ mà dùng không quá $i$ chuyến bay. Lúc này ta có $2$ trường hợp như sau : * $dp[u][i] = min_{(u, v) \in E} (dp[v][i] + c_{u, v})$ * $dp[u][i] = min_{v = 1} ^ n dp[v][i - 1] + (u - v) ^ 2$ Nếu **dijkstra** bình thường, rất dễ bị **TLE**. Vậy nên, ta sẽ xử lí riêng từng thường hợp với mỗi $1 \le i \le k$. ```c++ #include <bits/stdc++.h> using namespace std; #define MASK(x) (1LL << (x)) #define BIT(x, i) (((x) >> (i)) & 1) #define ALL(x) (x).begin(), (x).end() #define REP(i, n) for (int i = 0, _n = n; i < _n; ++i) #define FOR(i, a, b) for (int i = (a), _b = (b); i <= _b; ++i) #define FORD(i, a, b) for (int i = (a), _b = (b); i >= _b; --i) #define FORE(i, a, b) for (int i = (a), _b = (b); i < _b; ++i) #define debug(...) "[" << #__VA_ARGS__ ": " << (__VA_ARGS__) << "] " #define file(name) if (fopen(name".inp", "r")) { freopen(name".inp", "r", stdin); freopen(name".out", "w", stdout); } template <class A, class B> bool minimize(A &a, B b) { if (a > b) { a = b; return true; } return false; } template <class A, class B> bool maximize(A &a, B b) { if (a < b) { a = b; return true; } return false; } const int MAXN = 1e5 + 5; int N, K, M; vector <pair <int, int>> adj[MAXN]; long long dp[MAXN]; void dijkstra() { priority_queue <pair <long long, int>, vector <pair <long long, int>>, greater <pair <long long, int>>> q; FOR(i, 1, N) q.emplace(dp[i], i); while(!q.empty()) { auto [du, u] = q.top(); q.pop(); if(dp[u] != du) continue; for (auto [v, w] : adj[u]) if(minimize(dp[v], dp[u] + w)) { q.emplace(dp[v], v); } } } void you_make_it(void) { cin >> N >> M >> K; while(M--) { int u, v, w; cin >> u >> v >> w; adj[u].emplace_back(v, w); adj[v].emplace_back(u, w); } memset(dp, 0x3f, sizeof dp); dp[1] = 0; dijkstra(); while(K--) { vector <long long> f(dp, dp + N + 1); // vector f sẽ có nhiệm vụ lưu lại trạng thái dp trước FOR(i, 1, N) { FOR(j, 1, N) { minimize(dp[i], f[j] + 1LL * (i - j) * (i - j)); // Xử lí trường hợp 2 } } dijkstra(); // Xử lí trường hợp 1 } FOR(i, 1, N) cout << dp[i] << " "; } signed main() { #ifdef LOCAL freopen("TASK.inp", "r", stdin); freopen("TASK.out", "w", stdout); #endif auto start_time = chrono::steady_clock::now(); cin.tie(0), cout.tie(0) -> sync_with_stdio(0); you_make_it(); auto end_time = chrono::steady_clock::now(); cerr << "\nExecution time : " << chrono::duration_cast <chrono::milliseconds> (end_time - start_time).count() << "[ms]" << endl; return (0 ^ 0); } // Dream it. Wish it. Do it. ``` ### Subtask 3 : Để cải tiến trường hợp $2$ ở subtask $2$, ta sử dụng **CTDL** **Convex Hull Trick**. Nhận xét thấy rằng $dp[u][i] = dp[v][i - 1] + (u - v) ^ 2 = - 2uv + v ^ 2 + dp[v][i - 1] + u ^ 2$ ```c++ #include <bits/stdc++.h> using namespace std; #define MASK(x) (1LL << (x)) #define BIT(x, i) (((x) >> (i)) & 1) #define ALL(x) (x).begin(), (x).end() #define REP(i, n) for (int i = 0, _n = n; i < _n; ++i) #define FOR(i, a, b) for (int i = (a), _b = (b); i <= _b; ++i) #define FORD(i, a, b) for (int i = (a), _b = (b); i >= _b; --i) #define FORE(i, a, b) for (int i = (a), _b = (b); i < _b; ++i) #define debug(...) "[" << #__VA_ARGS__ ": " << (__VA_ARGS__) << "] " #define file(name) if (fopen(name".inp", "r")) { freopen(name".inp", "r", stdin); freopen(name".out", "w", stdout); } template <class A, class B> bool minimize(A &a, B b) { if (a > b) { a = b; return true; } return false; } template <class A, class B> bool maximize(A &a, B b) { if (a < b) { a = b; return true; } return false; } template <class num_t = int> struct convex_hull_trick { const num_t INF = (num_t) 1e18; struct line { num_t a, b; line(num_t a = 0, num_t b = 0) : a(a), b(b) {} num_t operator () (num_t x) { return a * x + b; } }; deque <line> lines; double slope(line x, line y) { return (long double) (y.b - x.b) / (long double) (x.a - y.a); } bool comp(line l1, line l2, line l3) { return slope(l1, l2) >= slope(l2, l3); // change <= to >= if you wanna get minimum } void add(num_t a, num_t b) { line tmp(a, b); while(lines.size() > 1 and comp(lines[lines.size() - 2], lines.back(), tmp)) lines.pop_back(); lines.push_back(tmp); } num_t query(num_t x) { if(lines.empty()) return -INF; while(lines.size() > 1 and lines[0](x) >= lines[1](x)) lines.pop_front(); // change <= to >= if you wanna get minimum return lines.front()(x); } }; const int MAXN = 1e5 + 5; int N, K, M; vector <pair <int, int>> adj[MAXN]; long long dp[MAXN]; void dijkstra() { priority_queue <pair <long long, int>, vector <pair <long long, int>>, greater <pair <long long, int>>> q; FOR(i, 1, N) q.emplace(dp[i], i); while(!q.empty()) { auto [du, u] = q.top(); q.pop(); if(dp[u] != du) continue; for (auto [v, w] : adj[u]) if(minimize(dp[v], dp[u] + w)) { q.emplace(dp[v], v); } } } void you_make_it(void) { cin >> N >> M >> K; while(M--) { int u, v, w; cin >> u >> v >> w; adj[u].emplace_back(v, w); adj[v].emplace_back(u, w); } memset(dp, 0x3f, sizeof dp); dp[1] = 0; dijkstra(); while(K--) { convex_hull_trick <long long> cht; FOR(i, 1, N) cht.add(-2 * i, dp[i] + 1LL * i * i); FOR(i, 1, N) { minimize(dp[i], cht.query(i) + 1LL * i * i); } dijkstra(); } FOR(i, 1, N) cout << dp[i] << " "; } signed main() { #ifdef LOCAL freopen("TASK.inp", "r", stdin); freopen("TASK.out", "w", stdout); #endif auto start_time = chrono::steady_clock::now(); cin.tie(0), cout.tie(0) -> sync_with_stdio(0); you_make_it(); auto end_time = chrono::steady_clock::now(); cerr << "\nExecution time : " << chrono::duration_cast <chrono::milliseconds> (end_time - start_time).count() << "[ms]" << endl; return (0 ^ 0); } // Dream it. Wish it. Do it. ``` ## Problem 3 : Tán gái 707 ### Subtask 1 + 2 ```c++ #include <bits/stdc++.h> using namespace std; #define MASK(x) (1LL << (x)) #define BIT(x, i) (((x) >> (i)) & 1) #define ALL(x) (x).begin(), (x).end() #define REP(i, n) for (int i = 0, _n = n; i < _n; ++i) #define FOR(i, a, b) for (int i = (a), _b = (b); i <= _b; ++i) #define FORD(i, a, b) for (int i = (a), _b = (b); i >= _b; --i) #define FORE(i, a, b) for (int i = (a), _b = (b); i < _b; ++i) #define debug(...) "[" << #__VA_ARGS__ ": " << (__VA_ARGS__) << "] " #define file(name) if (fopen(name".inp", "r")) { freopen(name".inp", "r", stdin); freopen(name".out", "w", stdout); } template <class A, class B> bool minimize(A &a, B b) { if (a > b) { a = b; return true; } return false; } template <class A, class B> bool maximize(A &a, B b) { if (a < b) { a = b; return true; } return false; } const int MAXN = 6e5 + 5; const int base = 3e5; int par[MAXN], sz[MAXN], cntx[MAXN], cnty[MAXN]; long long total_sum; int find(int u) { return u == par[u] ? u : par[u] = find(par[u]); } bool merge(int u, int v) { u = find(u), v = find(v); if(u == v) return false; if(sz[u] < sz[v]) swap(u, v); total_sum -= 1LL * cntx[u] * cnty[u] + 1LL * cntx[v] * cnty[v]; par[v] = u; sz[u] += sz[v]; cntx[u] += cntx[v]; cnty[u] += cnty[v]; total_sum += 1LL * cntx[u] * cnty[u]; return true; } long long calc(vector <pair <int, int>> tmp) { total_sum = 0; for (auto [x, y] : tmp) { sz[x] = sz[y + base] = 1; par[x] = x, par[y + base] = y + base; cntx[x] = 1, cnty[x] = 0; cntx[y + base] = 0, cnty[y + base] = 1; } for (auto [x, y] : tmp) merge(x, y + base); return total_sum; } void you_make_it(void) { int Q; cin >> Q; vector <pair <int, int>> tmp; while(Q--) { int x, y; cin >> x >> y; bool exist = false; REP(i, (int) tmp.size()) { if(tmp[i] == make_pair(x, y)) { exist = true; tmp.erase(tmp.begin() + i); break; } } if(!exist) tmp.emplace_back(x, y); cout << calc(tmp) << " "; } } signed main() { #ifdef LOCAL freopen("TASK.inp", "r", stdin); freopen("TASK.out", "w", stdout); #endif auto start_time = chrono::steady_clock::now(); cin.tie(0), cout.tie(0) -> sync_with_stdio(0); you_make_it(); auto end_time = chrono::steady_clock::now(); cerr << "\nExecution time : " << chrono::duration_cast <chrono::milliseconds> (end_time - start_time).count() << "[ms]" << endl; return (0 ^ 0); } // Dream it. Wish it. Do it. ```