--- tags: 成大高階競技程式設計 2021 image: https://i.imgur.com/IIj9BcL.png --- # 2021 高階競程 Contest #7 - 題解 ## [倍倍儲蓄法](https://judge.csie.ncku.edu.tw/problems/127) - Task Provider: D4nnyLee - Task Setter: D4nnyLee ### 首殺 team21_11 (00:02) ### 解法說明 這題用暴力破解就可以 AC。 ### 標準程式 :::spoiler ```cpp #include <iostream> using namespace std; constexpr int mod{1000000007}; void test_case() { int n; cin >> n; int ans = 1; for (int i{1}; i < n; ++i) ans = (ans * 2) % mod; cout << ans << '\n'; } int main() { int q; cin >> q; while (q--) test_case(); return 0; } ``` ::: ## [玩偶工廠](https://judge.csie.ncku.edu.tw/problems/128) - Task Provider: leo - Task Setter: leo ### 首殺 team21_12 (00:20) ### 解法說明 $\tbinom{n}{k}=\frac{n!}{k!(n-k)!}$ 因此可以利用 $O(N)$ 預處理 $0!\sim10^5!$, 且因為 $M$ 為質數, 根據費馬小定理可以得知 $a^{M-1}\equiv1\ (\text{mod}\ M)$ 因此 $n!$ 在模 $M$ 下的反元素為 $(n!)^{M-2}\ \text{mod}\ M$ 利用快速冪可以在 $O(N\log N)$ 的時間內得到 $0!~\sim10^5!$ 在模 $M$ 下的反元素。 每次詢問只需套公式即可得出解答。 ### 標準程式 :::spoiler ```cpp #include <iostream> using namespace std; long long fact[100001] = {1}, rev[100001] = {1}, MOD; inline long long power(long long a, long long b){ long long ans = 1; while(b){ if(b & 1) ans = ans * a % MOD; a = a * a % MOD; b >>= 1; } return ans; } inline long long C(int n, int k){ return fact[n] * rev[k] % MOD * rev[n - k] % MOD; } int main() { ios::sync_with_stdio(0); cin.tie(0); int t, n, k; cin >> t >> MOD; for(int i = 1; i <= 100000; i++){ fact[i] = fact[i - 1] * i % MOD; rev[i] = power(fact[i], MOD - 2); } while(t--){ cin >> n >> k; cout << C(n, k) << "\n"; } } ``` ::: ## [收納的智慧。Extreme](https://judge.csie.ncku.edu.tw/problems/129) - Task Provider: D4nnyLee - Task Setter: D4nnyLee ### 首殺 team21_11 (01:16) ### 解法說明 我們可以先回去參考[以前的題解](https://hackmd.io/@nckuacm/ryF3RbxvY#%E6%94%B6%E7%B4%8D%E7%9A%84%E6%99%BA%E6%85%A7%E3%80%82Hard), 從題解中可以知道 $dp_{i, 0} = 2 dp_{i - 1, 0} + 2 dp_{i - 1, 1} + dp_{n - 2, 0}$, 以及 $dp_{i, 1} = dp_{i - 1, 0} + dp_{i - 1, 1}$。 這題因為 $n$ 很大所以跟之前一樣跑時間複雜度為 $O(n)$ 的方法是不會過的, 而這題需要改成使用快速冪來解題。 從上面的兩個等式我們可以再寫出以下等式: $$ \left[ \begin{matrix} 2 & 2 & 1 \\ 1 & 1 & 0 \\ 1 & 0 & 0 \end{matrix} \right] \left[ \begin{matrix} dp_{i, 0} \\ dp_{i, 1} \\ dp_{i - 1, 0} \end{matrix} \right] = \left[ \begin{matrix} dp_{i + 1, 0} \\ dp_{i + 1, 1} \\ dp_{i, 0} \end{matrix} \right] $$ 也就可以得出: $$ \left[ \begin{matrix} 2 & 2 & 1 \\ 1 & 1 & 0 \\ 1 & 0 & 0 \end{matrix} \right]^{n - 1} \left[ \begin{matrix} dp_{1, 0} = 2 \\ dp_{1, 1} = 1\\ dp_{0, 0} = 1 \end{matrix} \right] = \left[ \begin{matrix} dp_{n, 0} \\ dp_{n, 1} \\ dp_{n - 1, 0} \end{matrix} \right] $$ $dp_{n, 0}$ 就是我們最後要的答案。 ### 標準程式 :::spoiler ```cpp #include <iostream> #include <vector> #include <array> using namespace std; constexpr int mod{1000000007}; void mul(array<array<long long, 3>, 3> a, array<array<long long, 3>, 3> b, array<array<long long, 3>, 3> &ret) { for (int i{0}; i < 3; ++i) for (int j{0}; j < 3; ++j) { ret[i][j] = 0; for (int k{0}; k < 3; ++k) { ret[i][j] += a[i][k] * b[k][j] % mod; ret[i][j] %= mod; } } } int main() { long long n; cin >> n; --n; array<array<long long, 3>, 3> A{2, 2, 1, 1, 1, 0, 1, 0, 0}, ans{1, 0, 0, 0, 1, 0, 0, 0, 1}; for (; n; n >>= 1) { if (n & 1) mul(ans, A, ans); mul(A, A, A); } cout << (2 * ans[0][0] % mod + ans[0][1] + ans[0][2]) % mod << '\n'; return 0; } ``` ::: ## [電力園區](https://judge.csie.ncku.edu.tw/problems/130) - Task Provider: leo - Task Setter: leo ### 首殺 team21_34 (01:12) ### 解法說明 因為是覆蓋所有點且周長最短的多邊形, 可以得知要求的是凸包的面積, 因此只要先求出凸包, 再利用 cross 計算有向面積加總, cross 得出來的面積是平行四邊形的面積, 因此需要除以 $2$ 才是正確的面積。 因為 double 精度不足的原因, 可以先將所有 cross 得到的有向面積加起來, 最後再除以 $2$,小數點後的數字只會有 $.0$ 或 $.5$, 直接以奇偶數特判輸出即可。 另一個方法是用 long double, 因為 long double 精度有 18 位。 但是 long double 運算速度較慢,因此較不推薦。 ### 標準程式 :::spoiler ```cpp #include <iostream> #include <algorithm> #include <vector> using namespace std; struct Point{ long long x, y; Point(long long x = 0, long long y = 0) : x(x), y(y){} Point operator-(const Point &b)const{ return Point(x - b.x, y - b.y); } long long cross(const Point &b)const{ return x * b.y - y * b.x; } }; bool cmp(const Point &a, const Point &b){ return (a.x < b.x) || (a.x == b.x && a.y < b.y); } vector<Point> convexHull(vector<Point> v){ int sz = 0; vector<Point> ans; sort(v.begin(), v.end(), cmp); for(size_t i = 0; i < v.size(); i++){ while(sz >= 2 && (ans[sz - 1] - ans[sz - 2]).cross(v[i] - ans[sz - 2]) <= 0) ans.pop_back(), sz--; ans.push_back(v[i]), sz++; } for(int i = int(v.size()) - 2, t = sz + 1; i >= 0; i--){ while(sz >= t && (ans[sz - 1] - ans[sz - 2]).cross(v[i] - ans[sz - 2]) <= 0) ans.pop_back(), sz--; ans.push_back(v[i]), sz++; } if(v.size() > 1) ans.pop_back(); return ans; } int main() { ios::sync_with_stdio(0); cin.tie(0); long long n, x, y, ans = 0; vector<Point> v, convex; cin >> n; while(n--){ cin >> x >> y; v.push_back(Point(x, y)); } convex = convexHull(v); for(int i = 0; i < convex.size(); i++) ans += convex[i].cross(convex[(i + 1) % convex.size()]); cout << (ans >> 1) << (ans & 1 ? ".5" : ".0") << "\n"; } ``` :::