---
tags: 進階班
---
# 10th 進階班上學期期末考題解
## A. 為各位的期末考獻上祝福
這題直接用 `long long` 或 `unsigned long long` 都是 `NA 59%`
正解:
1. 兩數同號,用 `unsigned long long`
2. 兩數異號,直接 `a + b`
3. 若 $a = b = -2^{63}$,需要特判,因為 `unsigned long long` 裝不下。
:::spoiler `code`
```cpp=
#include<bits/stdc++.h>
#define LL long long
using namespace std;
int main() {
LL a, b;
cin >> a >> b;
if (a < 0 ^ b < 0) cout << a + b;
else {
if (a == b && a == LLONG_MIN) cout << "-18446744073709551616";
else {
unsigned LL pa = abs(a), pb = abs(b);
if (a < 0) cout << '-';
cout << pa + pb;
}
}
return 0;
}
```
:::
另解:
使用毒瘤資料型態 `__int128` :poop:
為了讓各位覺得它有使用的價值,
先給各位看一下 `main()` 函數裡面的程式碼。
```cpp=
int main() {
__int128 a, b;
cin >> a >> b;
cout << a+b << '\n';
}
```
是不是很簡短!!!
但是它全部的程式碼卻有點複雜,
有興趣的可以參考看看。
:::spoiler `code`
```cpp=
#include <bits/stdc++.h>
using namespace std;
namespace int128IO {
istream& operator>>(istream& is, __int128& i) {
string s; is>>s; i = 0;
auto c=s.begin(); c+=(s[0]=='-');
for(; c!=s.end(); ++c) i=i*10+(*c-'0');
if(s[0]=='-') i=-i;
return is;
}
ostream& operator<<(ostream& os, __int128 i) {
string s; bool neg=false; if(i<0) neg=true, i=-i;
while(i) s+=('0'+i%10), i/=10;
if(neg) os<<'-';
for(auto c=s.rbegin();c!=s.rend();++c) os<<*c;
return os;
}
}
using namespace int128IO;
int main() {
__int128 a, b;
cin >> a >> b;
cout << a+b << '\n';
}
```
:::
## B. 迷宮
看到迷宮應該第一個會想到的是`BFS`,而既然題目需要問的是路徑,那麼只要在走的過程中把路徑記錄下來,最後在反推回去就可以得到答案了。
另外有一個小技巧就是如果從`B`開始走的話,你只要從起點往回推就不用再把路徑翻轉一次了。
:::spoiler `code`
```cpp=
pii d[4]{{1, 0}, {-1, 0}, {0, 1}, {0, -1}};
char unpth[4]{'L', 'R', 'U', 'D'};
inline void solve() {
int N, M; cin >> N >> M;
pii st, ed;
bool ch = 1;
vector<vector<pair<char ,int>>> vis(N, vector<pair<char, int>> (M, {' ', -1}));
for(int i = 0 ;i < N; i++)
for(int j = 0; j < M;j++) {
cin >> vis[i][j].f;
if(vis[i][j].f == 'A') st = MP(j, i); // make_pair
if(vis[i][j].f == 'B') ed = MP(j, i), vis[i][j].s = 1;
}
queue<pii> qq;
qq.EP(ed);
while(ch && !qq.empty()) {
pii a = qq.front(); qq.pop();
for(int i = 0; i < 4; i++) {
int mx = a.f + d[i].f;
int my = a.s + d[i].s;
if(mx < 0 || my < 0 || mx == M || my == N) continue;
if(vis[my][mx].f == '#') continue;
if(vis[my][mx].s != -1) continue;
qq.EP(mx, my);
vis[my][mx].s = i;
if(vis[my][mx].f == 'A') {ch = 0; break;}
}
}
if(!ch) {
string s1;
while(st != ed) {
auto a = vis[st.s][st.f];
s1 += unpth[a.s];
st.f -= d[a.s].f; st.s -= d[a.s].s;
}
cout << "YES" << endl << s1.size() << endl << s1;
}
else cout << "NO";
}
```
:::
## C. 持續演奏,直至心願實現
#### NA 10%
很簡單,把整個音譜跑過去,符合規則就是 $1$,不符合就是 $0$
#### NA 20%
暴搜 $w[i] = ?$ 的所有可能性
但是暴搜時,要記得:
1. 一個音符不能跨越兩個小節 $\implies$ `DFS` 時要有一個變數 `cur` 記錄當前拍數
2. 避免有「小數」拍子,可以將每個音節設定為 $\cfrac {16n}{m}$ 個十六分音符拍
3. 相對地,$k$ 分音符變 $\cfrac {16} k$ 拍
綜合以上 $3$ 點,我們可以知道 `check` 機制是:$cur \leq \cfrac{16n}{m}$
而這個 `DFS` 可以剪枝,如果當前節拍已經不符音譜規定,
那也不需要繼續遞迴後面的節拍了。
:::spoiler `code`
```cpp=
#include<bits/stdc++.h>
using namespace std;
constexpr int mod = 1e9 + 7;
int sum = 0, n, m, len, check;
string str;
void dfs(int i, int cur) {
if (i == len && cur == check) {sum++; return;}
cur %= check;
if (str[i] != '?') {
if (str[i] == 'M' && cur + 8 <= check) dfs(i + 1, cur + 8);
else if (str[i] == 'C' && cur + 4 <= check) dfs(i + 1, cur + 4);
else if (str[i] == 'Q' && cur + 2 <= check) dfs(i + 1, cur + 2);
else if (str[i] == 'S' && cur + 1 <= check) dfs(i + 1, cur + 1);
else return;
}
else {
if (cur + 8 <= check) dfs(i + 1, cur + 8);
if (cur + 4 <= check) dfs(i + 1, cur + 4);
if (cur + 2 <= check) dfs(i + 1, cur + 2);
if (cur + 1 <= check) dfs(i + 1, cur + 1);
}
}
int main() {
cin >> n >> m;
cin.get(), cin >> str;
len = str.size(), check = 16 / m * n;
dfs(0, 0), cout << sum;
return 0;
}
```
:::
#### NA 40%
從這裡開始就是使用 `DP` 了
在剛剛的 NA 20% 解中,我們可以發現:當前節拍數 `cur` 是很重要的,
所以我們可以建一個陣列,存下當前音符之下,各種拍數的可能數各為多少。
前 $20\%$ 的 `DP` 測資只有 $?$,所以只要知道 $?$ 的轉移式即可:
$dp[i][j] = dp[i - 1][j - 8] + dp[i - 1][j - 4] + dp[i - 1][j - 2] + dp[i - 1][j - 1]$
$i$ 是指當前第幾音符,$j$ 是指第幾節拍,
但是要注意溢位問題喔!
需判斷 $j - 8,\;j - 4,\;j - 2,\;j - 1\geq 0$
#### AC
先說一下,NA 60% 是給複雜度歪掉的人用的
直接來講如何 AC。
從 NA 40% 的解法可以知道:只差 $w[i]\neq \;?$ 的轉移式就 AC 了。
不難理解,其實 $?$ 的轉移式就是把 $\{M,\;C,\;Q,\;S\}$ 的轉移式相加
把他們分開就好。
因此,所有 $DP$ 轉移式如下:
$\displaystyle \begin{cases} \displaystyle \begin{cases} dp[i][j] = 0\\
dp[i][j] = dp[i][j] + dp[i - 1][j - 8]
\quad\quad if\quad j \geq 8\\
dp[i][j] = dp[i][j] + dp[i - 1][j - 4]
\quad\quad if\quad j \geq 4\\
dp[i][j] = dp[i][j] + dp[i - 1][j - 2]
\quad\quad if\quad j \geq 2\\
dp[i][j] = dp[i][j] + dp[i - 1][j - 1]
\quad\quad if\quad j \geq 1\\
\end{cases}
\quad\quad if\quad w[i] =\;?\\\\
dp[i][j] = dp[i - 1][j - 8]
\quad\quad if\quad (j \geq 8\quad and\quad w[i]=\;M)\\
dp[i][j] = dp[i - 1][j - 4]\quad\quad if\quad (j \geq 4\quad and\quad w[i]=\;C)\\
dp[i][j] = dp[i - 1][j - 2]\quad\quad if\quad (j \geq 2\quad and\quad w[i]=\;Q)\\dp[i][j] = dp[i - 1][j - 1]\quad\quad if\quad (j \geq 1\quad and\quad w[i]=\;S)\\dp[i][j] = 0\quad\quad\quad\quad\quad\;\;\;\quad\quad else \end{cases}$
:::spoiler `code_直觀`
```cpp=
#include<bits/stdc++.h>
#define LL long long
using namespace std;
constexpr int mod = 1e9 + 7;
LL dp[2][145] = {1}; //dp[0][0] = 1, 16 * n / m <= 144
int main() {
int n, m;
cin >> n >> m;
int p = 16 * n / m;
string w; cin >> w;
const int len = w.size();
for (size_t i = 0; i < w.size(); i++) {
for (int j = 1; j <= p; j++) {
if(w[i] == '?'){
dp[~i & 1][j] = 0;
if(j >= 8) dp[~i & 1][j] += dp[i & 1][j - 8];
if(j >= 4) dp[~i & 1][j] += dp[i & 1][j - 4];
if(j >= 2) dp[~i & 1][j] += dp[i & 1][j - 2];
if(j >= 1) dp[~i & 1][j] += dp[i & 1][j - 1];
dp[~i & 1][j] %= mod;
}
else if(w[i] == 'M' && j >= 8)
dp[~i & 1][j] = dp[i & 1][j - 8];
else if(w[i] == 'C' && j >= 4)
dp[~i & 1][j] = dp[i & 1][j - 4];
else if(w[i] == 'Q' && j >= 2)
dp[~i & 1][j] = dp[i & 1][j - 2];
else if(w[i] == 'S' && j >= 1)
dp[~i & 1][j] = dp[i & 1][j - 1];
else dp[~i & 1][j] = 0;
}
dp[~i & 1][0] = dp[~i & 1][p];
}
cout << dp[len & 1][0];
return 0;
}
```
:::
:::spoiler `code_稍微包裝`
```cpp=
#include<bits/stdc++.h>
#define LL long long
using namespace std;
constexpr int mod = 1e9 + 7;
int dp[2][145] = {1}; //dp[0][0] = 1, 16 * n / m <= 144
int main() {
int n, m; const int num[4] = {1, 2, 4, 8};
const string tile = "SQCM";
cin >> n >> m;
int p = 16 * n / m;
string w; cin >> w;
const int len = w.size();
for (size_t i = 0; i < w.size(); i++) {
for (int j = 1; j <= p; j++) {
bool flag = false;
if (w[i] == '?') dp[~i & 1][j] = 0, flag = true;
for (int k = 0; k < 4; k++) {
if (w[i] == tile[k] && j >= num[k]) {
dp[~i & 1][j] = dp[i & 1][j - num[k]];
flag = true; break;
}
else if (w[i] == '?' && j >= num[k]) {
dp[~i & 1][j] += dp[i & 1][j - num[k]];
dp[~i & 1][j] %= mod;
}
}
if (!flag) dp[~i & 1][j] = 0;
}
dp[~i & 1][0] = dp[~i & 1][p], dp[~i & 1][p] = 0;
}
cout << dp[len & 1][0];
return 0;
}
```
:::
## D. 簡單的奇偶問題
### 觀念
這題有三個重點**奇偶數的性質**、**位元運算**和**p的範圍**。
1. **奇偶數的性質**: ($O=odd$、$E=even$)
1. $O+O=E$
2. $O+E=O$
3. $E+O=O$
4. $E+E=E$
2. **位元運算**: (`xor` 的性質)
1. $1$^$1 = 0$
2. $1$^$0 = 1$
3. $0$^$1 = 1$
4. $0$^$0 = 0$
3. **p的範圍**: $1\leq p\leq 30$,而 `int` 能儲存的最大值為 $2^{31}-1$,代表有 $31$ 個位元可以使用
觀察完以上三點性質後就會發現,
如果以 $0$ 當成偶數,$1$ 當成奇數,
把每一組數列儲存到一個 `int` 裡面,
並將兩者取 `xor` 就可以達到奇偶數相加判斷的效果。
### 實作
這題因為是區間更新,
所以要處理一下 $r-l\geq 2$ 時的 `xor` 要怎麼處理。
其實使要記得一個性質 $a$^$b$\^$a = b$,
也就是只要該數字被 `xor` 偶數次,
那可以視為沒有被運算過,
所以程式碼可以被這樣實作
```cpp=
int update(int origin_value, int l, int r, int update_value) { // [l, r]
if((r-l+1)&1)
orgin_value ^= update_value;
return origin_value;
}
```
這時還有另一個問題,
當在查詢時,
如果發生了 `ql > r || l > qr` 的情況,
那要回傳甚麼?
這個問題有兩個解法
1. 直接寫一個新的資料結構
2. 再次利用位元運算的性質
針對第一種解法,
其實就是在加上一個布林值來判斷是不是從出界的遞迴的回傳值。
所實作的資料結構如下
:::spoiler `code`
```cpp=
template<class T> struct Num {
bool NaN; // false -> is NaN
T num;
Num(pair<bool, T> __init) {
NaN = __init.first;
num = __init.second;
}
Num(T __value) {*this = Num({true, __value});}
Num(bool __NaN) {*this = Num({__NaN, T()});}
Num() {*this = Num({false, T()});}
friend Num operator^(const Num a, const Num b) {
return a.NaN||b.NaN ? Num(a.NaN&&b.NaN?a.num^b.num:(a.NaN?a.num:b.num)) : Num(false);
}
bool isNaN() {return !NaN;}
T getV() {return num;}
};
```
:::
第二種解法可以觀察一下位元運算,
1. $0$^$0 = 0$
2. $1$^$0 = 1$
可以看到不管什麼數字 `xor` $0$ 還是原本的數字,
所以可以直接回傳 $0$。
整體的程式碼的實作如下
:::spoiler `solution1`
```cpp=
#include <bits/stdc++.h>
using namespace std;
template<class T> struct Num {
bool NaN; // false -> is NaN
T num;
Num(pair<bool, T> __init) {
NaN = __init.first;
num = __init.second;
}
Num(T __value) {*this = Num({true, __value});}
Num(bool __NaN) {*this = Num({__NaN, T()});}
Num() {*this = Num({false, T()});}
friend Num operator^(const Num a, const Num b) {
return a.NaN||b.NaN ? Num(a.NaN&&b.NaN?a.num^b.num:(a.NaN?a.num:b.num)) : Num(false);
}
bool isNaN() {return !NaN;}
T getV() {return num;}
};
using Int = Num<int>;
const int mxN = 1e5;
struct segtree {
#define lc (id<<1)
#define rc (id<<1|1)
#define mid ((l+r)/2)
int sz;
Int seg[mxN<<2], tag[mxN<<2];
void pull(int id, int l, int r) {
if(l != r) {
seg[id] = seg[lc]^seg[rc];
}
}
void push(int id, int l, int r) {
if(l != r) {
if((mid-l+1)&1)
seg[lc] = seg[lc]^tag[id];
if((r-mid)&1)
seg[rc] = seg[rc]^tag[id];
tag[lc] = tag[lc]^tag[id];
tag[rc] = tag[rc]^tag[id];
}
tag[id] = Int(false);
}
void build(vector<Int>& vt, int id, int l, int r) {
if(l == r) {
seg[id] = vt[l-1];
return;
}
build(vt, lc, l, mid), build(vt, rc, mid+1, r);
pull(id, l, r);
}
Int qry(int id, int l, int r, int ql, int qr) {
push(id, l, r);
if(ql <= l && r <= qr)
return seg[id];
if(ql > r || l > qr)
return Int(false);
return qry(lc, l, mid, ql, qr)^qry(rc, mid+1, r, ql, qr);
}
void upd(int id, int l, int r, int ql, int qr, Int v) {
push(id, l, r);
if(ql <= l && r <= qr) {
if((r-l+1)&1)
seg[id] = seg[id]^v;
tag[id] = tag[id]^v;
return;
}
if(ql > r || l > qr)
return;
upd(lc, l, mid, ql, qr, v), upd(rc, mid+1, r, ql, qr, v);
pull(id, l, r);
}
segtree(int _sz, vector<Int>& vt) {
sz = _sz;
build(vt, 1, 1, sz);
}
Int qry(int ql, int qr) {return qry(1, 1, sz, ql, qr);}
void upd(int ql, int qr, Int v) {upd(1, 1, sz, ql, qr, v);}
#undef lc
#undef rc
#undef mid
};
int calc(Int num, int p) {
int ret = 0, x = num.getV();
for(int i = 0; i < p; ++i) {
if(x&(1<<i))
++ret;
}
return ret;
}
int main() {
cin.tie(0), ios_base::sync_with_stdio(0);
int n, q, p;
cin >> n >> q >> p;
vector<Int> vt(n);
for(auto& num : vt) {
int t = 0;
for(int x, i = 0; i < p; ++i) {
cin >> x;
if(x&1)
t|=(1<<i);
}
num = Int(t);
}
segtree tree(n, vt);
while(q--) {
int o, l, r;
cin >> o;
if(o) {
cin >> l >> r;
int t = 0;
for(int x, i = 0; i < p; ++i) {
cin >> x;
if(x&1)
t|=(1<<i);
}
tree.upd(l, r, t);
} else {
cin >> l >> r;
cout << calc(tree.qry(l, r), p) << '\n';
}
}
}
```
:::
:::spoiler `solution2`
```cpp=
#include <bits/stdc++.h>
using namespace std;
const int mxN = 1e5;
struct segtree {
#define lc (id<<1)
#define rc (id<<1|1)
#define mid ((l+r)/2)
int sz;
int seg[mxN<<2], tag[mxN<<2];
void pull(int id, int l, int r) {
if(l != r) {
seg[id] = seg[lc]^seg[rc];
}
}
void push(int id, int l, int r) {
if(l != r) {
if((mid-l+1)&1)
seg[lc] ^= tag[id];
if((r-mid)&1)
seg[rc] ^= tag[id];
tag[lc] ^= tag[id];
tag[rc] ^= tag[id];
}
tag[id] = 0;
}
void build(vector<int>& vt, int id, int l, int r) {
if(l == r) {
seg[id] = vt[l-1];
return;
}
build(vt, lc, l, mid), build(vt, rc, mid+1, r);
pull(id, l, r);
}
int qry(int id, int l, int r, int ql, int qr) {
push(id, l, r);
if(ql <= l && r <= qr)
return seg[id];
if(ql > r || l > qr)
return 0;
return qry(lc, l, mid, ql, qr)^qry(rc, mid+1, r, ql, qr);
}
void upd(int id, int l, int r, int ql, int qr, int v) {
push(id, l, r);
if(ql <= l && r <= qr) {
if((r-l+1)&1)
seg[id] ^= v;
tag[id] ^= v;
return;
}
if(ql > r || l > qr)
return;
upd(lc, l, mid, ql, qr, v), upd(rc, mid+1, r, ql, qr, v);
pull(id, l, r);
}
segtree(int _sz, vector<int>& vt) {
sz = _sz;
build(vt, 1, 1, sz);
}
int qry(int ql, int qr) {return qry(1, 1, sz, ql, qr);}
void upd(int ql, int qr, int v) {upd(1, 1, sz, ql, qr, v);}
#undef lc
#undef rc
#undef mid
};
int calc(int num) {
int ret = 0;
while(num) {
if(num&1)
++ret;
num >>= 1;
}
return ret;
}
int main() {
cin.tie(0), ios_base::sync_with_stdio(0);
int n, q, p;
cin >> n >> q >> p;
vector<int> vt(n);
for(auto& num : vt) {
int t = 0;
for(int x, i = 0; i < p; ++i) {
cin >> x;
if(x&1)
t|=(1<<i);
}
num = t;
}
segtree tree(n, vt);
while(q--) {
int o, l, r;
cin >> o;
if(o) {
cin >> l >> r;
int t = 0;
for(int x, i = 0; i < p; ++i) {
cin >> x;
if(x&1)
t|=(1<<i);
}
tree.upd(l, r, t);
} else {
cin >> l >> r;
cout << calc(tree.qry(l, r)) << '\n';
}
}
}
```
:::
## E. 金斧頭一砍八
#### NA 60%
照著題敘一個一個把人砍掉即可
:::spoiler `code`
```cpp=
#include<bits/stdc++.h>
using namespace std;
constexpr int mod = 1e9 + 7;
int main() {
int T, n;
cin >> T;
while (T--) {
cin >> n;
bool alive[n + 1];
memset(alive, 1, sizeof(alive));
bool flag = true;
for (int i = 1, tmp = 0; tmp < n - 1; i == n ? i = 1 : i++) {
if (alive[i]) {
flag = !flag;
if (flag) alive[i] = 0, tmp++;
}
}
for (int i = 1; i <= n; i++) {
if (alive[i]) {cout << i << '\n'; break;}
}
}
return 0;
}
```
:::
#### AC
##### solve 1
其實砍人有其規律:
如果把編號 $1\sim n$ 砍完一圈算一輪,
假設現在是第 $r$ 輪,還有 $p$ 人未蹲下,且未蹲下的人中,編號最小的是 $k$,則:
1. 未蹲下的人中,相鄰編號差距為 $2^{r - 1}$
2. 從編號 $1\sim n$ 砍完,人會剩 $[\cfrac p2]$ 人
3. 若 $p$ 為奇數,則砍完人後,最小編號會變成 $k + 2^r$
如此就可以省去 `for` 迴圈從 $1$ 跑到 $n$ 的過程。
有趣的是,如果 $2^r$ 用 `pow` 的話,不僅慢,還可能有精度問題 (他是浮點數)
因此我們可以把 $2^0\sim 2^{30}$ 次方建出來,加快速度。
(由於 $n\in int$ 及第 3. 點證明,因此可以得知 $r\leq 30$)
:::spoiler `code`
```cpp=
#include<bits/stdc++.h>
#define LL long long
#define mem(x) memset(x, 0, sizeof(x))
#define all(x) x.begin(), x.end()
#define f first
#define s second
using namespace std;
constexpr int mod = 1e9 + 7;
int main() {
cin.tie(0), ios_base::sync_with_stdio(false);
int pow[31] = {1};
for(int i = 1; i <= 30; i++) pow[i] = pow[i - 1] * 2;
int T, n;
cin >> T;
while (T--) {
cin >> n;
int k = 1, r = 1;
while (n > 1) {
if (n % 2 == 1) { // if(n & 1)
k += pow[r]; // k += (1 << r)
}
n /= 2, r++; // n >>= 1, r++
}
cout << k << '\n';
}
return 0;
//上方程式碼註解是給進階班的 code,進階班可以不用建表就 AC
}
```
:::
##### solve 2
如果你花了很久的時間找數列的規律,
可以發現答案是:${1}, \{1, 3\}, \{1, 3, 5, 7\}, \{1, 3, 5, 7, 9, 11, 13, 15\}...$
可以轉換成數學式:$(n - 2^{[{\log_2}^n]})\times 2 + 1$
:::spoiler `code`
```cpp=
#include<bits/stdc++.h>
#define LL long long
using namespace std;
constexpr int mod = 1e9 + 7;
int main() {
cin.tie(0), ios_base::sync_with_stdio(false);
int pow[31] = {1};
for (int i = 1; i <= 30; i++) pow[i] = pow[i - 1] * 2;
int T, n;
cin >> T;
while (T--) {
cin >> n;
cout << (n - pow[__lg(n)]) * 2 + 1 << '\n';
//cout << (n - (1 << __lg(n)) << 1 | 1) << '\n';
}
return 0;
//上方程式碼註解是給進階班的 code,進階班可以不用建表就 AC
}
```
:::
## F. 么棟陸砲彈
#### AC
簡化這題題目:給定一陣列,有區間修改和單點查值的操作,輸出查值。
這題既然是區間修改和單點查值,顯然是 `BIT` 水題
:::spoiler `code`
```cpp=
int arr[200001], bit[200001], p;
int lowbit(int x) {return x & -x;}
void build() {
for (int i = 1; i <= p; i++) {
int tmp = i;
while (tmp <= p) bit[tmp] += arr[i], tmp += lowbit(tmp);
}
}
int query(int x) {return x ? bit[x] + query(x - lowbit(x)) : 0;}
void upd(int l, int r, int x) {
while (l <= p) bit[l] += x, l += lowbit(l);
while (r <= p) bit[r] -= x, r += lowbit(r);
}
int main() {
cin.tie(nullptr), ios_base::sync_with_stdio(false);
int q, s, l, r, x;
bool f, op;
cin >> p >> q >> s;
for (int i = 1; i <= p; i++) cin >> arr[i];
for (int i = p; i; i--) arr[i] -= arr[i - 1];
build();
for (int i = q + s; i; i--) {
cin >> op;
if (op) cin >> x, cout << query(x) << '\n';
else cin >> l >> r >> x >> f, upd(l, r + 1, f ? x : x / -4);
}
return 0;
}
```
:::
## G. Gura's Factory
### 觀念
這題的重點就是如何處理
$$ C_{m_i} = (S_j + 1\times |j-k|) + (F_k + 1\times |k-m_i|) $$
這串看起來頗複雜的數學式子。
因為造成數學式子複雜的原因是絕對值,
我們就先討論並分解,
討論時要注意 $k < m_i \wedge k < j$ 和 $k > m_i \wedge k > j$ 的條件。
1. $k < m_i \leq j\implies C_{m_i} = S_j + F_k + j + m_i - 2\times k$
2. $k < j \leq m_i\implies C_{m_i} = S_j + F_k + j + m_i - 2\times k$
3. $k > m_i \geq j\implies C_{m_i} = S_j + F_k - j - m_i + 2\times k$
4. $k > m_i \geq j\implies C_{m_i} = S_j + F_k - j - m_i + 2\times k$
分解完就會發現只要判斷 $m_i$ 和 $j$ 之間的關係不會影響答案,
只有 $k$ 為最大或最小會影響,
因此可以將 $m_i$、$j$ 的部分做預處理,
再利用線段樹針對 $k$ 的大小做查詢就會找到答案。
### 預處理
其實就是窮舉 $j$ 所有的可能,
並針對每一種可能對 $m_i$ 做查詢。
:::spoiler `code`
```cpp=
int s[mxN], f[mxN], ki[mxN], ik[mxN], saj[mxN], smj[mxN]; // ki -> k>i, ik -> i>k, saj -> s+j, smj -> s-j
for(int j = 0; j < n; ++j) {
saj[j] = s[j] + (j+1);
smj[j] = s[j] - (j+1);
}
segtree tsa(saj, n), tsm(smj, n);
for(int k = 1; k <= n; ++k) {
ik[k-1] = ki[k-1] = INT_MAX;
if(k+1 <= n)
ik[k-1] = tsa.qry(k+1, n) + (f[k-1]-2*k);
if(k-1 >= 1)
ki[k-1] = tsm.qry(1, k-1) + (f[k-1]+2*k);
}
```
:::
### 實作
跟預處理的概念一模一樣,
只是這次會給定 $k$ 的值,
所以只要針對該值進行查詢就好。
`Time Complexity`: $O(N\log N + Q\log N)$
:::spoiler `solution1`
```cpp=
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const int mxN = 1e5;
int s[mxN], f[mxN], ki[mxN], ik[mxN], saj[mxN], smj[mxN]; // ki -> k>i, ik -> i>k, saj -> s+j, smj -> s-j
struct segtree {
#define lc (id<<1)
#define rc (id<<1|1)
#define mid ((l+r)/2)
int sz;
int seg[mxN<<2];
void pull(int id, int l, int r) {
if(l != r)
seg[id] = min(seg[lc], seg[rc]);
}
void build(int *dt, int id, int l, int r) {
if(l == r) {
seg[id] = dt[l-1];
return;
}
build(dt, lc, l, mid), build(dt, rc, mid+1, r);
pull(id, l, r);
}
segtree(int *dt, int _sz) : sz(_sz) {build(dt, 1, 1, sz);}
int qry(int id, int l, int r, int ql, int qr) {
if(ql <= l && r <= qr)
return seg[id];
if(ql > r || l > qr)
return INT_MAX;
return min(qry(lc, l, mid, ql, qr), qry(rc, mid+1, r, ql, qr));
}
int qry(int ql, int qr) {return qry(1, 1, sz, ql, qr);}
#undef lc
#undef rc
#undef mid
};
int main() {
cin.tie(0), ios_base::sync_with_stdio(0);
int n, q;
cin >> n >> q;
for(int i = 0; i < n; ++i)
cin >> s[i];
for(int i = 0; i < n; ++i)
cin >> f[i];
for(int j = 0; j < n; ++j) {
saj[j] = s[j] + (j+1);
smj[j] = s[j] - (j+1);
}
segtree tsa(saj, n), tsm(smj, n);
for(int k = 1; k <= n; ++k) {
ik[k-1] = ki[k-1] = INT_MAX;
if(k+1 <= n)
ik[k-1] = tsa.qry(k+1, n) + (f[k-1]-2*k);
if(k-1 >= 1)
ki[k-1] = tsm.qry(1, k-1) + (f[k-1]+2*k);
}
segtree tik(ik, n), tki(ki, n);
while(q--) {
int m;
cin >> m;
int q1 = INT_MAX, q2 = INT_MAX;
if(m-1 >= 1)
q1 = tik.qry(1, m-1)+m;
if(m+1 <= n)
q2 = tki.qry(m+1, n)-m;
cout << min(q1, q2) << '\n';
}
}
```
:::
實作到這裡會突然意識到一件事,
因為這題根本不需要修改,
所以根本不用用到線段樹 :poop:,
直接用**單調列隊**就可以解了。
`Time Complexity`: $O(N+Q)$
:::spoiler `solution2`
```cpp=
#include<bits/stdc++.h>
#define LL long long
#define mem(x) memset(x, 0, sizeof(x))
#define all(x) x.begin(), x.end()
#define f first
#define s second
using namespace std;
constexpr int mod = 1e9 + 7;
int n, q, s[100001], f[100001];
vector<int> jk(100002, 2e9), kj(100002, 2e9);
int main() {
cin.tie(nullptr), ios_base::sync_with_stdio(false);
cin >> n >> q;
for (int i = 1; i <= n; i++) cin >> s[i];
for (int i = 1; i <= n; i++) cin >> f[i];
int mi;
for(int i = 1; i <= n; i++){
jk[i] = s[i] + i;
kj[i] = s[i] - i;
}
for(int i = 1; i <= n; i++){
jk[n - i + 1] = min(jk[n - i + 2], jk[n - i + 1]);
kj[i] = min(kj[i - 1], kj[i]);
}
vector<int> pre_k(n + 2, 2e9), suf_k(n + 2, 2e9);
for (int k = 1; k <= n; k++) {
pre_k[k] = kj[k - 1] + 2 * k + f[k]; //mi, j < k
suf_k[k] = jk[k + 1] - 2 * k + f[k]; //k < mi, j
}
for (int i = 1; i <= n; i++) {
suf_k[i] = min(suf_k[i - 1], suf_k[i]);
pre_k[n - i + 1] = min(pre_k[n - i + 2], pre_k[n - i + 1]);
}
while (q--) {
cin >> mi;
cout << min(-mi + pre_k[mi + 1], mi + suf_k[mi - 1]) << '\n';
}
return 0;
}
```
:::
## H. 神奇的卡牌遊戲
### NA 55%
比賽的時候,如果你看到單筆測資,判斷又只有少數個的話,**在競賽容許的情況下**,你可以直接丟判斷,或是用`random`跑判斷,可以喇到一些分數。
```cpp=
cout << "YES" <<endl;
```
### NA 90%
喇完55%後可以發現後面的幾乎都是`NO`,所以你可以再加個判斷
```cpp=
if(K == 1) cout << "YES" <<endl;
else cout << "NO" << endl;
```
### AC
這次單純是題目出爛,還是要學一下正解怎麼做的:poop:
如果你有仔細看題目的話你會發現`複雜度`的地方很奇怪
再仔細想想的話你會發現,如果前`n-1`個卡牌都已經決定好是否翻了,若第`n-k`個卡牌反面的話你就必須要翻第`n`個卡牌。
所以你只要對前`k`個卡牌的是否翻的可能性都做一遍,之後就可以不斷地維護左界。
:::spoiler `code`
```cpp=
void upd(int K, int pos, vector<bool> &vc) {
vc[pos] = !vc[pos];
for (int i = 1; i <= K; i++) {
if (pos + i < int(vc.size())) vc[i + pos] = !vc[i + pos];
if (pos - i >= 0) vc[pos - i] = !vc[pos - i];
}
}
int main () {
int N, K;
cin >> N >> K;
vector<bool> vc(N), cmp;
string st;
cin >> st;
bool ok = false;
for (int i = 0; i < N; i++) vc[i] = (st[i] - '0');
for (int i = 0; i < (1 << K); i++) {
cmp = vc;
for (int j = 0; j < K; j++)
if ((i >> j) & 1) upd(K, j, cmp);
for (int j = K; j < N; j++)
if (!cmp[j - K]) upd(K, j, cmp);
int cnt = 0;
for (auto i : cmp) cnt += i;
if (cnt == N) {ok = true; break;}
}
if (ok) cout << "YES" << endl;
else cout << "NO" << endl;
}
```
:::
複雜度 : $O(2^kN)$