# 2018 TOI 入營考題解
## A. 化學元素分析(Chemical Analysis)[TIOJ 2051](https://tioj.ck.tp.edu.tw/problems/2051)
括號序列的 parser,可用 [Shunting-yard algorithm](https://en.wikipedia.org/wiki/Shunting-yard_algorithm)
只需要一個 stack。
- 時間複雜度:$O(N^2 \log N)$ (應該ㄅ)
- 空間複雜度:$O(N)$ (其實我不確定?)
因為這題範圍很小所以我沒有寫複雜度更好的解(也沒有想 XD)
```cpp=1
#include<bits/stdc++.h>
using namespace std;
int main()
{
stack<map<string, int>> st;
string s;
cin >> s;
cout << s << endl;
s = "(" + s + ")";
for (int i = 0; i < s.size(); ++i) {
if (isalpha(s[i])) {
if (islower(s[i + 1]))
st.push({{s.substr(i++, 2), 1}});
else
st.push({{s.substr(i, 1), 1}});
} else if (isdigit(s[i])) {
int x = 0;
while (isdigit(s[i]))
x = 10 * x + s[i++] - '0';
--i;
for (auto & p : st.top())
p.second *= x;
} else if (s[i] == '(') {
st.emplace();
} else if (s[i] == ')') {
map<string, int> a, b;
do {
b = st.top();
st.pop();
for (auto p : b)
a[p.first] += p.second;
} while (b.size());
st.push(a);
}
}
assert(st.size() == 1);
for (auto [a, b] : st.top())
cout << a << ":" << b << endl;
}
```
## B. 排列第幾個?(Permutation)[TIOJ 2052](https://tioj.ck.tp.edu.tw/problems/2052)
這是一個排列組合 / 數論問題。
$\pi$ 是該些字母排列中的第 $K$個,表示比他字典序小的排列有 $K$ 個,所以問題其實是統計比他小的排列數量。
比 $\pi$ 字典序小的排列 $\sigma$ 可以照他跟 $\pi$ 的共同前綴長度來分類,假設是 $i$ 好了,
$\sigma$ 必須滿足
- $\forall j \in [1, i], \sigma_j = \pi_j$
- $\sigma_{i+1} < \pi_{i+1}$
可以發現這樣的 $\sigma$ 數量是可以輕鬆的用可重複的排列數算出來的。
(練習:請寫出這個公式)
實做上會遇到的問題是我們需要做除法,而 $\mod D$ 時只有跟 $D$互質的數字有模反元素,怎麼辦呢?
可以利用這題的性質:每個我們要加到答案的數字都是整數。
因此所有做除法的地方必定是整除的,於是我們可以把每個數字跟 $D$共同的質因數都提出來,他們的乘除用冪次的加減即可。
換句話說,當 $D=\prod_{i=1}^{\omega(D)} {p_i}^{e_i}$ 時,對於一個數字 $n=q \prod_{i=1}^{\omega(D)} {p_i}^{e_i(n)}$,其中 $q$ 跟 $D$ 互質,我們用一個向量 $\{q, e_1(n), e_2(n), \cdots, e_{\omega(D)}(n) \}$ 來表示它。
- 時間複雜度:$O(N \Sigma \omega(D))$, $\Sigma = 52$ 是字元集大小,$\omega(D)$ 是 $D$ 的[相異質因數數量](http://mathworld.wolfram.com/DistinctPrimeFactors.html),在這題的範圍內,$\omega(D) \le 5$。(練習:請證明這個不等式)
- 空間複雜度:$O(N \omega(D))$
```cpp=1
#include<bits/stdc++.h>
using namespace std;
void exgcd(int a, int &x, int b, int &y, int & g)
{
if (b) {
exgcd(b, y, a%b, x, g);
y -= a/b * x;
} else {
x = 1;
y = 0;
g = a;
}
}
int minv(int a, int m)
{
int x, y, g;
exgcd(m, x, a, y, g);
assert(g == 1);
return (y % m + m) % m;
}
int qpow(int a, int b, int m)
{
int r = 1 % m;
for (;b;b>>=1,a=a*a%m)
if (b&1)
r=r*a%m;
return r;
}
int D;
vector<int> p;
vector<int> e;
const int N = 1087;
struct mint
{
int r;
vector<int> q;
mint(int a = 1)
{
q.assign(e.size(), 0);
for (int i = 0; i < e.size(); ++i) {
while (a % p[i] == 0)
a /= p[i], ++q[i];
}
r = a;
}
mint operator * (const mint & x) const
{
mint ret;
ret.r = r * x.r % D;
for (int i = 0; i < p.size(); ++i)
ret.q[i] = q[i] + x.q[i];
return ret;
}
mint operator / (const mint & x) const
{
mint ret;
ret.r = r * minv(x.r, D) % D;
for (int i = 0; i < p.size(); ++i)
ret.q[i] = q[i] - x.q[i];
return ret;
}
int operator () (void) const
{
int ret = r;
for (int i = 0; i < p.size(); ++i)
ret = ret * qpow(p[i], q[i], D) % D;
return ret;
}
} w[N], iw[N];
void precalc()
{
int ot = D;
for (int i = 2; i * i <= ot; ++i)
if (ot % i == 0) {
p.push_back(i);
e.push_back(0);
for (; ot % i == 0; ot /= i, e.back()++);
}
if (ot > 1)
p.push_back(ot), e.push_back(1);
}
string o, s;
int cnt[26*2];
int main()
{
cin >> D >> s;
precalc();
for (char c = 'a'; c <= 'z'; ++c) o += c;
for (char c = 'A'; c <= 'Z'; ++c) o += c;
sort(begin(o), end(o));
for (char & c : s)
c = find(begin(o), end(o), c) - begin(o);
int n = s.size();
iw[0] = iw[1] = w[0] = w[1] = mint(1);
for (int i = 2; i < N; ++i) {
w[i] = mint(i);
iw[i] = w[0] / w[i];
}
mint tot;
assert(tot() == 1);
int ans = 0;
for (int i = n - 1; i >= 0; --i) {
int len = n - i;
tot = tot * w[len - 1];
tot = tot * iw[++cnt[s[i]]];
for (int j = 0; j < s[i]; ++j)
if (cnt[j]) {
(ans += (tot * w[cnt[j]])()) %= D;
/* (len-1)! / ((cnt[k] - (k == j))! for k=0 to 51) */
}
}
cout << ans << endl;
}
```
## C. 費氏數列(Fibonacci)[TIOJ 2053](https://tioj.ck.tp.edu.tw/problems/2053)
這是一個二階[線性遞迴](http://www.csie.ntnu.edu.tw/~u91029/Sequence2.html#2)問題,十分經典。
可以用矩陣快速冪求解。
- 時間複雜度:$O(\log N)$
- 空間複雜度:$O(1)$
```cpp=1
#include<bits/stdc++.h>
using namespace std;
const int MOD = 1e9 + 7;
const int W = 2;
typedef long long ll;
struct mat
{
ll a[W][W];
mat() : a{} {}
mat operator * (const mat & rhs) const
{
mat ret;
for (int k = 0; k < W; ++k)
for (int i = 0; i < W; ++i)
for (int j = 0; j < W; ++j)
(ret.a[i][j] += a[i][k] * rhs.a[k][j]) %= MOD;
return ret;
}
} A, V;
int main()
{
int n;
cin >> V.a[1][0] >> V.a[0][0] >> A.a[0][1] >> A.a[0][0] >> n;
A.a[1][0] = 1;
n -= 2;
for (; n; n >>= 1, A = A * A)
if (n & 1)
V = A * V;
cout << V.a[0][0] << endl;
}
```
## D. 最大矩形涵蓋(cover) [TIOJ 2054](https://tioj.ck.tp.edu.tw/problems/2054)
對於任意選取的一個矩形,你總是能把它往下平移直到碰到一個被蓋住的點,過程中矩形內的點只會變多或不變,不會變少。
往左平移同理。
於是你只需要考慮右上角座標是 $(a = x_i, b = y_j)$ 的那些矩形,共有 $O(N^2)$ 個。
對每個矩形你要算有幾個點被他覆蓋,這是一個矩形求和問題,可以用二維[前綴和](https://oi-wiki.org/basic/prefix-sum/)解決。
因為這題的值域很大,需要[離散化](https://oi-wiki.org/misc/discrete/)。
- 時間複雜度:$O(N^2)$
- 空間複雜度:$O(N^2)$
```cpp=1
#include<bits/stdc++.h>
using namespace std;
const int N = 3087;
int x[N], y[N], s[N][N];
int main()
{
int n, l, w;
cin >> n >> l >> w;
vector<int> vx = {-1}, vy = {-1};
for (int i = 1; i <= n; ++i) {
cin >> x[i] >> y[i];
vx.push_back(x[i]);
vy.push_back(y[i]);
}
sort(vx.begin(), vx.end());
vx.resize(unique(vx.begin(), vx.end()) - vx.begin());
sort(vy.begin(), vy.end());
vy.resize(unique(vy.begin(), vy.end()) - vy.begin());
for (int i = 1; i <= n; ++i) {
int a = lower_bound(vx.begin(), vx.end(), x[i]) - vx.begin();
int b = lower_bound(vy.begin(), vy.end(), y[i]) - vy.begin();
s[a][b]++;
}
int ans = 0;
for (int i = 1, li = 0; i < vx.size(); ++i) {
while (vx[i] - vx[li + 1] > w)
++li;
for (int j = 1, lj = 0; j < vy.size(); ++j) {
s[i][j] += s[i - 1][j] + s[i][j - 1] - s[i - 1][j - 1];
while (vy[j] - vy[lj + 1] > l)
++lj;
ans = max(ans, s[i][j] - s[i][lj] - s[li][j] + s[li][lj]);
}
}
cout << ans << endl;
}
```
## E. 直升機(Helicopter) [TIOJ 2055](https://tioj.ck.tp.edu.tw/problems/2055)
就 [RMQ 問題](https://oi-wiki.org/topic/rmq/)
$O(N \log N)$ 的作法有幾百種
可以用競賽中常用到的線段樹或 Sparse Table
很裸的線段樹題吧(?)
所以我不寫線段樹(?)
離線單調隊列 + 二分搜
有夠短(?)
- 時間複雜度:$O(N \log N)$
- 空間複雜度:$O(N)$
```cpp=1
#include<bits/stdc++.h>
using namespace std;
const int N = 1e5 + 87;
int n, h[N], ans[N];
vector<pair<int,int>> q[N];
deque<int> m;
int main()
{
ios::sync_with_stdio(0);
cin.tie(0);
cin >> n;
for (int i = 1; i <= n; ++i) {
cin >> h[i];
++h[i];
}
for (int i = 0; i < n; ++i) {
int l, r;
cin >> l >> r;
q[l].emplace_back(r, i);
}
for (int l = n; l >= 1; --l) {
while (m.size() && h[m[0]] >= h[l])
m.pop_front();
m.push_front(l);
for (const auto & [r, i] : q[l])
ans[i] = h[*(upper_bound(begin(m), end(m), r) - 1)];
}
for (int i = 0; i < n; ++i)
cout << ans[i] << '\n';
}
```