# 112 學年度建中資訊校內第一次模擬賽 題解
## [pA. 老鼠與貓咪](https://tioj.ck.tp.edu.tw/problems/2325)
Setter:becaido
### Subtask 1:$n,q\le 3000$
題目可以簡化成 $x=a_0$,每次 $x=|x-a_i|$,問最後 $x$ 是多少。
暴力做是 $O(nq)$ 的。
### Subtask 2:$a_i\le a_{i+1}$($0\le i<n$)
發現每次 $x\leq a_i$,所以 $x$ 會變成 $a_i-x$,答案會是 $a_i$ 一正一負相加的總和。
例如當 $p=5$,答案會等於 $a_5-(a_4-(a_3-(a_2-(a_1-a_0))))=a_5-a_4+a_3-a_2+a_1-a_0$。
可以用前綴和 $O(1)$ 算,複雜度 $O(n+q)$。
### Subtask 3:無其他限制
$a_0$ 會是 $0\sim 10^6$ 的其中一個,可以把這些數字變成一個區間,丟到數線上,離線更新與處理查詢。
每次與一個 $a_i$ 做運算時,根據這個區間在原點右邊或原點左邊,可以知道它要往左移還是往右移 $a_i$ 格,可能會有區間不跨越原點與跨越原點兩種情況。
對於跨越原點這種情況,整段區間會是原點左邊與原點右邊的區間所組成的,利用並查集可以把小的那一段 merge 到大的那一段。
![](https://hackmd.io/_uploads/Bkx2oFof6.png =420x)
時間複雜度可以到 $O(n+C\alpha(C)+q)$,$C$ 為值域,空間複雜度 $O(n+C+q)$。
:::spoiler code
```cpp=
#include <bits/stdc++.h>
using namespace std;
const int N = 1e6 + 5;
const int MAX = 1e6;
int n, q;
int a[N], ans[N];
vector<pair<int, int>> ask[N];
int L, R, l, r;
int to[MAX + 5];
int dsu(int x) {
return x == to[x] ? x : (to[x] = dsu(to[x]));
}
int main() {
ios::sync_with_stdio(false), cin.tie(0);
cin >> n >> q;
for (int i = 1; i <= n; i++) cin >> a[i];
for (int i = 1; i <= q; i++) {
int x, p;
cin >> x >> p;
ask[p].emplace_back(x, i);
}
iota(to, to + MAX + 1, 0);
R = r = MAX;
for (int i = 1; i <= n; i++) {
if (l >= 0) l -= a[i], r -= a[i];
else l += a[i], r += a[i];
if (l < 0 && r > 0) {
if (-l <= r) {
for (int j = l; j < 0; j++) to[j + (L - l)] = -j + (L - l);
L -= l, l = 0;
} else {
for (int j = 1; j <= r; j++) to[j + (R - r)] = -j + (R - r);
R -= r, r = 0;
}
}
for (auto [x, id] : ask[i]) {
x = dsu(x);
ans[id] = x + (l - L);
}
}
for (int i = 1; i <= q; i++) cout << abs(ans[i]) << '\n';
}
```
:::
:::spoiler Bonus
[luogu P8264](https://www.luogu.com.cn/problem/P8264)
:::
## [pB. 送外賣](https://tioj.ck.tp.edu.tw/problems/2324)
Setter:becaido
[題目出處](https://atcoder.jp/contests/cf17-final/tasks/cf17_final_j)
### Subtask 1:$n\leq 1000$
題目在問的是最小生成樹邊權和。
可以以每個人為根,暴力找出所有邊,之後排序完用 Kruskal,複雜度 $O(n^2\log n)$。
也可以利用[運送蛋餅](https://tioj.ck.tp.edu.tw/problems/2164)的想法,用 Prim 做到 $O(n^2)$。
### Subtask 2:$u_i=1,v_i=i+1$
樹是一個星狀圖,令 $w_1=0,w_i$ 是 $i$ 與 $1$ 的邊權,令 $A_i=a_i+w_i$。
$W(i,j)=D(i,j)+a_i+a_j=A_i+A_j$,選擇 $A_i$ 最小的那個人,與其他 $n-1$ 個人相連即是最小生成樹。
複雜度 $O(n)$。
### Subtask 3:$u_i=i,v_i=i+1$
樹會是一條鏈。
有兩種做法:
做法一:把所有可能在最小生成樹上的邊加進來,排序後用 Kruskal。
利用分治的想法,每次把鏈切成左右兩邊,令 $A_i=a_i+w'_i$,$w'_i$ 是 $i$ 離中心點的距離。
左右兩邊各選一點 $i_l,i_r$,相連的邊權是 $A_{i_l}+A_{i_r}$,會發現選 $A_i$ 最小的那個人跟另外一個區間的所有人相連會是最好的,如果不小心跟自己這個區間的人連邊也沒關係,因為在此時的邊權會比實際的大,排序後在前面的邊權比較小,會比自己早連。
處理完一個大區間可以遞迴左右兩個區間,總共會有 $O(n\log n)$ 條邊。
時間複雜度 $O(n\log^2 n)$,空間複雜度 $O(n\log n)$。
做法二:Borůvka's 演算法
Borůvka's 演算法的想法:剛開始會有 $n$ 個連通塊,每一輪每個連通塊選擇連出去其他連通塊最小的那條邊,之後把有連邊的連通塊 merge 起來。一直做這個動作,直到連通塊數量等於一,此時連的 $n-1$ 條邊即為最小生成樹。
每一輪連通塊數量至少減半,做 $O(\log n)$ 輪會結束。
把鏈上的每個人剛開始都視為一個連通塊,在每一輪,要維護的是每個人連向非自己連通塊的最小邊權,可以維護最小值與次小值求得,每輪 $O(n)$,時間複雜度 $O(n\log n)$,空間複雜度 $O(n)$。
### Subtask 4:無其他限制
一樣有兩個做法。
做法一:重心剖分,令 $A_i=a_i+w'_i$,$w'_i$ 為 $i$ 至重心的距離。
每次只要加入 $A_i$ 最小的與這層其他人連的邊,之後拔掉重心遞迴。
最後所有邊排序一次,做一次 Kruskal 即可。時間複雜度 $O(n\log^2 n)$,空間複雜度 $O(n\log n)$。
做法二:一樣是 Borůvka,每一輪做換根 DP,一樣維護最小值與次小值。時間複雜度 $O(n\log n)$,空間複雜度 $O(n)$。
:::spoiler code (重剖)
```cpp=
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const int N = 2e5 + 5;
int n;
ll ans;
int a[N];
vector<pair<int, int>> adj[N];
vector<tuple<ll, int, int>> e;
bool vs[N];
int id, tag[N];
void divide(int pos) {
int sz = 0, cent = 0;
{
queue<int> q;
q.push(pos), tag[pos] = ++id;
while (q.size()) {
int pos = q.front();
q.pop();
sz++;
for (auto [np, w] : adj[pos]) if (vs[np] == 0 && tag[np] != id) {
q.push(np);
tag[np] = id;
}
}
auto dfs = [&](auto dfs, int pos, int fa)->int {
int cnt = 1, mx = 0;
for (auto [np, w] : adj[pos]) if (vs[np] == 0 && np != fa) {
int sub = dfs(dfs, np, pos);
cnt += sub;
mx = max(mx, sub);
}
mx = max(mx, sz - cnt);
if (mx <= sz / 2) cent = pos;
return cnt;
};
dfs(dfs, pos, pos);
vs[cent] = 1;
}
ll mn = LLONG_MAX, p = 0;
queue<pair<int, ll>> q;
q.emplace(cent, 0), tag[cent] = ++id;
while (q.size()) {
auto [pos, sum] = q.front();
q.pop();
if (a[pos] + sum < mn) {
mn = a[pos] + sum;
p = pos;
}
for (auto [np, w] : adj[pos]) if (vs[np] == 0 && tag[np] != id) {
q.emplace(np, sum + w);
tag[np] = id;
}
}
q.emplace(cent, 0), tag[cent] = ++id;
while (q.size()) {
auto [pos, sum] = q.front();
q.pop();
e.emplace_back(a[pos] + sum + mn, pos, p);
for (auto [np, w] : adj[pos]) if (vs[np] == 0 && tag[np] != id) {
q.emplace(np, sum + w);
tag[np] = id;
}
}
for (auto [np, w] : adj[cent]) if (vs[np] == 0) divide(np);
}
int to[N], h[N];
int dsu(int x) {
return x == to[x] ? x : (to[x] = dsu(to[x]));
}
bool merge(int a, int b) {
a = dsu(a), b = dsu(b);
if (a == b) return 0;
if (h[a] < h[b]) swap(a, b);
to[b] = a;
h[a] += (h[a] == h[b]);
return 1;
}
int main() {
ios::sync_with_stdio(false), cin.tie(0);
cin >> n;
for (int i = 1; i <= n; i++) cin >> a[i];
for (int i = 1; i < n; i++) {
int a, b, w;
cin >> a >> b >> w;
adj[a].emplace_back(b, w);
adj[b].emplace_back(a, w);
}
divide(1);
sort(e.begin(), e.end());
iota(to + 1, to + n + 1, 1);
for (auto [w, i, j] : e) if (merge(i, j)) ans += w;
cout << ans << '\n';
}
```
:::
:::spoiler Bonus
[luogu P6199](https://www.luogu.com.cn/problem/P6199)
:::
## [pC. 偶像選拔祭](https://tioj.ck.tp.edu.tw/problems/2328)
Setter:becaido
### Subtask 1:$P=0$
沒有成本的話每個人可以自己組成一個團體。
答案為 $\sum\limits_{i=1}^N a_i$ 複雜度 $O(N)$。
### Subtask 2:$N\leq 500$
令 $dp_i$ 是前 $i$ 個人所有分法中最大的總收益,$S(l,r)$ 是 $l\sim r$ 的人前 $\min(K,r-l+1)$ 小的 $a_i$ 總和。
可以列出 DP 式:$dp_i=\max\limits_{0\le j<i}(dp_j+S(j+1,i)-P)$。
$S(j+1,i)$ 如果每次都排序求前 $\min(K,i-j)$ 小的人,複雜度為 $O(N^3\log N)$。
如果改用 nth_element 可以進一步壓到 $O(N^3)$。
### Subtask 3:$N\leq 2000$
由右往左枚舉 $j$,用一個 priority_queue 維護前 $\min(K,i-j)$ 小的人。
如果 pq 的大小超過 $K$ 了,那就把頂端的元素 pop 掉。
複雜度 $O(N^2\log N)$。
### Subtask 4:$a_i\leq a_{i+1}$($1\leq i<N$)
先把整個陣列反轉,變成遞減的,比較好解說。
分出 $[1,K],[K+1,2K],\dots,[(i-1)K+1,iK],\dots,[\lfloor\frac{N}{K}\rfloor K+1,N]$ 的區間,每次計算答案,計算完後把最後面兩個區間合併,繼續計算答案。
可以證明每種取法都是在固定區間數量時最好的取法。
複雜度 $O(N)$。
### Subtask 5:$K=1$
回到 DP,會發現說這個 DP 滿足凹四邊形的條件,當 $i_1<i_2$,轉移到 $j$ 時 $i_1$ 比 $i_2$ 好,那在 $j$ 之後的所有人也一定是從 $i_1$ 轉移比較好,因為 $\min\limits_{i_1<i\leq j+1}a_i-\min\limits_{i_1<i\leq j}a_i\leq \min\limits_{i_2<i\leq j+1}a_i-\min\limits_{i_2<i\leq j}a_i$,最小值的遞減量一定是 $i_2$ 的比較大(或相同),所以當某個時刻 $i_1$ 比 $i_2$ 好,那 $i_2$ 在之後一定不會優於 $i_1$ 了。
利用 stack 維護轉移點與轉移區間,做凹四邊形優化,如果用線段樹算區間最小值時間複雜度 $O(n\log^2 n)$、空間複雜度 $O(n)$;用稀疏表時間、空間複雜度 $O(n\log n)$。
### Subtask 6:無其他限制
只有 $j-i_1,j-i_2\geq K$ 的部分滿足凹四邊形。
$j-i<K$ 的部分可以用前綴和與單調隊列做,$j-i\geq K$ 的部分可以在處理 $i$ 轉移到的範圍時順便看此時誰會轉移到 $i+K$,然後把 $i+K$ 從 stack 裡移除。
區間前 $K$ 小的總和可以用持久化線段樹做。
時間複雜度 $O(N\log^2 N)$,空間複雜度 $O(N\log N)$。
:::spoiler code
```cpp=
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const int N = 2e5 + 5;
const int H = __lg(N) + 1;
int n, k;
ll P, pre[N], dp[N];
int a[N], id[N], p[N];
struct Node {
int cnt;
ll sum;
int ls, rs;
} node[4 * H * N];
int sz, root[N];
void upd(int &pos, int l, int r, int p) {
node[++sz] = node[pos];
pos = sz;
if (l == r) {
node[pos].cnt++;
node[pos].sum += a[id[p]];
return;
}
int mid = (l + r) / 2;
if (p <= mid) upd(node[pos].ls, l, mid, p);
else upd(node[pos].rs, mid + 1, r, p);
node[pos].cnt = node[node[pos].ls].cnt + node[node[pos].rs].cnt;
node[pos].sum = node[node[pos].ls].sum + node[node[pos].rs].sum;
}
ll que(int pl, int pr, int l, int r, int k) {
if (l == r) return node[pr].sum - node[pl].sum;
int mid = (l + r) / 2;
int lcnt = node[node[pr].ls].cnt - node[node[pl].ls].cnt;
ll lsum = node[node[pr].ls].sum - node[node[pl].ls].sum;
if (lcnt >= k) return que(node[pl].ls, node[pr].ls, l, mid, k);
else return lsum + que(node[pl].rs, node[pr].rs, mid + 1, r, k - lcnt);
}
ll cal(int l, int i) {
if (i - l <= k) return dp[l] + pre[i] - pre[l] - P;
return dp[l] + que(root[l], root[i], 1, n, k) - P;
}
struct seg {
int p, l, r;
seg() {}
seg(int p, int l, int r) : p(p), l(l), r(r) {}
};
deque<pair<int, ll>> dq;
vector<seg> st;
int from[N];
int main() {
ios::sync_with_stdio(false), cin.tie(0);
cin >> n >> k >> P;
for (int i = 1; i <= n; i++) cin >> a[i];
for (int i = 1; i <= n; i++) pre[i] = pre[i - 1] + a[i];
iota(id + 1, id + n + 1, 1);
sort(id + 1, id + n + 1, [&](int l, int r) {
return a[l] < a[r];
});
for (int i = 1; i <= n; i++) p[id[i]] = i;
for (int i = 1; i <= n; i++) {
root[i] = root[i - 1];
upd(root[i], 1, n, p[i]);
}
dq.emplace_back(0, 0);
st.emplace_back(seg(0, k, n));
for (int i = 1; i <= n; i++) {
dp[i] = cal(from[i], i);
if (dq.front().first + k < i) dq.pop_front();
dp[i] = max(dp[i], dq.front().second + pre[i] - P);
while (dq.size() && dq.back().second <= dp[i] - pre[i]) dq.pop_back();
dq.emplace_back(i, dp[i] - pre[i]);
if (i + k > n) continue;
from[i + k] = st.back().p;
st.back().l = max(st.back().l, i + k + 1);
if (st.back().l > st.back().r) st.pop_back();
while (st.size() && cal(i, st.back().r) >= cal(st.back().p, st.back().r)) st.pop_back();
if (st.size() == 0) st.emplace_back(seg(i, i + k + 1, n));
else {
auto [p, l, r] = st.back();
while (l < r) {
int mid = (l + r) / 2;
if (cal(p, mid) > cal(i, mid)) r = mid;
else l = mid + 1;
}
st.back().l = l;
if (i + k + 1 <= l - 1) st.emplace_back(seg(i, i + k + 1, l - 1));
}
}
cout << dp[n] << '\n';
}
```
:::
:::spoiler Bonus
現在沒有出道成本 $P$ 了,但是節目限制要剛好有 $M(1\leq M\leq N)$ 個團體,請問總收益最大是多少呢?
:::
## [pD. 轉生到異世界的我,竟然變成了農夫約翰的牛 ?!](https://tioj.ck.tp.edu.tw/problems/2323)
Idea:PCC
Setter:becaido
### Subtask 1:$n,q\leq 3000$
每次詢問暴力算總和,複雜度 $O(nq)$。
### Subtask 2:只會有 $2\ p$ 的操作
可以利用單調隊列維護每個人能看到的左界與右界,詢問時用前綴和 $O(1)$ 算,複雜度 $O(n+q)$。
### Subtask 3:$h_i<h_{i+1}$($1\leq i<n$)
每個人能看到的範圍是 $[1,i]$,可以用 BIT 或線段樹維護區間和,複雜度 $O(n+q\log n)$。
### Subtask 4:無其他限制
先用單調隊列求出每個人的範圍,然後用 BIT 或線段樹維護區間和,複雜度 $O(n+q\log n)$。
:::spoiler code
```cpp=
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const int N = 2e5 + 5;
int n, q;
int h[N], w[N];
int L[N], R[N];
ll bit[N];
void upd(int pos, int x) {
for (; pos <= n; pos += pos & -pos) bit[pos] += x;
}
ll que(int pos) {
ll re = 0;
for (; pos; pos -= pos & -pos) re += bit[pos];
return re;
}
ll que(int l, int r) {
return que(r) - que(l - 1);
}
int main() {
ios::sync_with_stdio(false), cin.tie(0);
cin >> n >> q;
for (int i = 1; i <= n; i++) cin >> h[i];
for (int i = 1; i <= n; i++) cin >> w[i];
vector<int> st;
for (int i = 1; i <= n; i++) {
while (st.size() && h[i] >= h[st.back()]) st.pop_back();
L[i] = (st.size() ? st.back() : 0) + 1;
st.emplace_back(i);
}
st.clear();
for (int i = n; i >= 1; i--) {
while (st.size() && h[i] >= h[st.back()]) st.pop_back();
R[i] = (st.size() ? st.back() : n + 1) - 1;
st.emplace_back(i);
}
for (int i = 1; i <= n; i++) upd(i, w[i]);
while (q--) {
int ty, p, x;
cin >> ty >> p;
if (ty == 1) {
cin >> x;
upd(p, x - w[p]);
w[p] = x;
} else {
cout << que(L[p], R[p]) << '\n';
}
}
}
```
:::
:::spoiler Bonus
$h_i$ 也有可能被修改,你能順利求出區間和嗎?
:::
## [pE. 博士愛序列](https://tioj.ck.tp.edu.tw/problems/2326)
Setter:becaido
### Subtask 1:$N,Q\leq 500$
每次詢問 $O(N^2)$ 檢查整個區間,複雜度 $O(N^2Q)$。
### Subtask 2:$N\leq 3000$
對所有 $l$ 存 $r$ 跟詢問編號,然後離線處理。
由右往左加入元素,可以 $O(n)$ 更新每個人與左邊幾個人滿足條件,利用前綴和可以 $O(1)$ 回答詢問。
複雜度 $O(N^2+Q)$。
### Subtask 3:$a_i<32$
可以利用前綴和計算詢問的區間內 $0\sim 31$ 分別有幾個,然後花 $O(32^2)$ 次去檢查。
複雜度 $O(NC + QC^2)$。
### Subtask 4:$l=1$
可離線處理詢問,由左往右加入元素,每次加入一個元素,有兩種更新方式:
一個是用 $\text{cnt}_i$ 記錄之前 $i$ 出現過了幾次,然後對加入的人 $O(C)$ 去檢查。
一個是用 $\text{cnt}_i$ 記錄新加入的人如果是 $i$ 答案會加多少,然後花 $O(C)$ 更新 $\text{cnt}$。
複雜度 $O(NC+Q)$。
### Subtask 5:無其他限制
可以用莫隊的想法,但是每次加入 / 刪除元素都要花 $O(C)$ 算太久了,想要平衡更新與查詢的複雜度。
假設現在莫隊的指針在 $[L,R]$,想要把 $R$ 右移到 $R'$,把加入的元素跟影響到的區間用數對表示可以寫成 $(R+1,[L,R]),(R+2,[L,R+1]),\dots,(R'-1,[L,R'-2]),(R',[L,R'-1])$。
會發現每個人影響到的區間左界都是 $L$,右界都是自己 $-1$,並且 $[L,R]$ 這個區間可以寫成 $[1,R]-[1,L-1]$ 的形式,於是可以把每個人影響到的區間拆成兩個左界是 $1$ 的區間,第一個的右界是自己 $-1$,可以統一算後用前綴和直接求,第二個的右界則是 $L-1$,那可以在 $L-1$ push 進 $[R+1,R']$ 這個區間,等到所有詢問都 push 完後統一求。
對於 $R$ 往左,$L$ 往左、往右的情況也是類似的,只需要微調 push 的位置與正負號。
在統一算的時候只有 $N$ 個元素要更新,所以可以花 $O(C)$ 更新,這樣詢問就只要 $O(1)$。
等到所有詢問都有結果了,因為我們算的是變化量,所以要對詢問做一次前綴和。
時間複雜度 $O(N\sqrt{Q}+NC)$。
這種方法稱為莫隊二次離線。
:::spoiler code
```cpp=
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const int N = 1e5 + 5;
const int MAX = 2048;
const int Block = sqrt(N);
int n, q;
int A, B, K;
ll ans[N];
int a[N];
tuple<int, int, int> ask[N];
int cnt[MAX];
ll pre1[N], pre2[N];
vector<tuple<int, int, int, int>> op[N];
int main() {
ios::sync_with_stdio(false), cin.tie(0);
cin >> n >> q;
cin >> A >> B >> K;
for (int i = 1; i <= n; i++) cin >> a[i];
for (int i = 1; i <= q; i++) {
auto &[l, r, id] = ask[i];
cin >> l >> r;
id = i;
}
sort(ask + 1, ask + q + 1, [&](auto lhs, auto rhs) {
return get<0>(lhs) / Block != get<0>(rhs) / Block ? get<0>(lhs) < get<0>(rhs) : get<1>(lhs) < get<1>(rhs);
});
for (int i = 1, L = 1, R = 0; i <= q; i++) {
auto [l, r, id] = ask[i];
if (R < r) op[L - 1].emplace_back(R + 1, r, id, -1), R = r;
if (L > l) op[R].emplace_back(l, L - 1, id, 1), L = l;
if (R > r) op[L - 1].emplace_back(r + 1, R, id, 1), R = r;
if (L < l) op[R].emplace_back(L, l - 1, id, -1), L = l;
}
for (int i = 1; i <= n; i++) {
for (int j = 0; j < MAX; j++) cnt[j] += ((a[i] ^ j ^ A) + (a[i] ^ j ^ B) >= K);
if (i < n) pre1[i + 1] = pre1[i] + cnt[a[i + 1]];
pre2[i] = pre2[i - 1] + cnt[a[i]];
for (auto [l, r, id, coef] : op[i]) for (int j = l; j <= r; j++) ans[id] += coef * cnt[a[j]];
}
for (int i = 1, L = 1, R = 0; i <= q; i++) {
auto [l, r, id] = ask[i];
ans[id] += ans[get<2>(ask[i - 1])];
if (R < r) ans[id] += pre1[r] - pre1[R], R = r;
if (L > l) ans[id] -= pre2[L - 1] - pre2[l - 1], L = l;
if (R > r) ans[id] -= pre1[R] - pre1[r], R = r;
if (L < l) ans[id] += pre2[l - 1] - pre2[L - 1], L = l;
}
for (int i = 1; i <= q; i++) cout << ans[i] << '\n';
}
```
:::
## [pF. 堆雪人](https://tioj.ck.tp.edu.tw/problems/2327)
Setter:becaido
### Subtask 1:$x\leq 10^ 5,b=1$
$b^y=1,A=\dfrac{a^x}{b^y}=\dfrac{a^x}{1}=a^x,B=0$
只要算 $a^x\text{ mod }N$ 是多少就好了,直接乘是 $O(x)$ 的。
### Subtask 2:$b=1$
用快速冪,$O(\log C)$。
### Subtask 3:$y=1,b<N,N$ 是質數
$B=a^x\text{ mod }b$
$a^x=Ab+B,a^x-B=Ab,A=\dfrac{a^x-B}{b}$
$A\equiv \dfrac{a^x-B}{b}\equiv (a^x-B)b^{-1}\ (\text{mod }N)$
複雜度 $O(\log C)$。
### Subtask 4:$y=1$
把運算都變成 $c_1b^1+c_0b^0$ 的形式。
令 $a^x=C_1b+C_0$,那 $A=\lfloor\dfrac{a^x}{b}\rfloor=C_1$,$B=C_0$。
記得在運算時 $c_1$ 要 $\text{mod }N$,複雜度 $O(\log C)$。
### Subtask 5:無其他限制
把運算都寫成 $c_yb^y+c_{y-1}b^{y-1}+\dots+c_1b^1+c_0b^0$ 的形式,兩個數字相乘相當於在做一個多項式乘法,可以 $O(y^2)$ 求出。
最後 $A$ 會等於 $C_y$,$B$ 等於剩下的人相加,記得運算過程 $c_y$ 要 $\text{mod }N$。
複雜度 $O(y^2\log C)$。
令一個可能的解法是把 $b^y$ 展開來變成大數,然後做 Subtask 4,不過我沒試,常數可能要夠好才會過。
:::spoiler code
```cpp=
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
using ds = vector<int>;
int a, b, x, y, N, M;
ds init(int val) {
ds re(y + 1, 0);
for (int i = 0; i < y; i++) {
re[i] = val % b;
val /= b;
}
re[y] = val % N;
return re;
}
ds operator * (const ds &l, const ds &r) {
vector<ll> sum(2 * y + 1, 0);
for (int i = 0; i <= y; i++) {
for (int j = 0; j <= y; j++) sum[i + j] += 1ll * l[i] * r[j];
for (int j = i; j < y; j++) {
sum[j + 1] += sum[j] / b;
sum[j] %= b;
}
for (int j = y; j <= i + y; j++) sum[j] %= N;
}
for (int i = 2 * y; i > y; i--) sum[i - 1] = (sum[i - 1] + sum[i] * b) % N;
ds re(y + 1, 0);
for (int i = 0; i <= y; i++) re[i] = sum[i];
return re;
}
ds power(int d, int up) {
ds re, nd;
re = init(1), nd = init(d);
while (up) {
if (up & 1) re = re * nd;
nd = nd * nd;
up >>= 1;
}
return re;
}
int power(int d, int up, int mod) {
int re = 1;
d %= mod;
while (up) {
if (up & 1) re = 1ll * re * d % mod;
d = 1ll * d * d % mod;
up >>= 1;
}
return re;
}
int main() {
ios::sync_with_stdio(false), cin.tie(0);
cin >> a >> b >> x >> y >> N >> M;
if (b == 1) {
cout << power(a, x, N) << ' ' << 0 << '\n';
return 0;
}
ds ans = power(a, x);
int A = ans[y], B = 0;
for (int i = y - 1; i >= 0; i--) B = (1ll * B * b + ans[i]) % M;
cout << A << ' ' << B << '\n';
}
```
:::
:::spoiler Bonus
$y$ 可以到 $10^5$
:::