10th 進階班上學期期末考題解
A. 為各位的期末考獻上祝福
這題直接用 long long
或 unsigned long long
都是 NA 59%
正解:
- 兩數同號,用
unsigned long long
- 兩數異號,直接
a + b
- 若 ,需要特判,因為
unsigned long long
裝不下。
code
另解:
使用毒瘤資料型態 __int128
Image Not Showing
Possible Reasons
- The image file may be corrupted
- The server hosting the image is unavailable
- The image path is incorrect
- The image format is not supported
Learn More →
為了讓各位覺得它有使用的價值,
先給各位看一下 main()
函數裡面的程式碼。
是不是很簡短!!!
但是它全部的程式碼卻有點複雜,
有興趣的可以參考看看。
code
B. 迷宮
看到迷宮應該第一個會想到的是BFS
,而既然題目需要問的是路徑,那麼只要在走的過程中把路徑記錄下來,最後在反推回去就可以得到答案了。
另外有一個小技巧就是如果從B
開始走的話,你只要從起點往回推就不用再把路徑翻轉一次了。
code
C. 持續演奏,直至心願實現
NA 10%
很簡單,把整個音譜跑過去,符合規則就是 ,不符合就是
NA 20%
暴搜 的所有可能性
但是暴搜時,要記得:
- 一個音符不能跨越兩個小節
DFS
時要有一個變數 cur
記錄當前拍數
- 避免有「小數」拍子,可以將每個音節設定為 個十六分音符拍
- 相對地, 分音符變 拍
綜合以上 點,我們可以知道 check
機制是:
而這個 DFS
可以剪枝,如果當前節拍已經不符音譜規定,
那也不需要繼續遞迴後面的節拍了。
code
NA 40%
從這裡開始就是使用 DP
了
在剛剛的 NA 20% 解中,我們可以發現:當前節拍數 cur
是很重要的,
所以我們可以建一個陣列,存下當前音符之下,各種拍數的可能數各為多少。
前 的 DP
測資只有 ,所以只要知道 的轉移式即可:
是指當前第幾音符, 是指第幾節拍,
但是要注意溢位問題喔!
需判斷
AC
先說一下,NA 60% 是給複雜度歪掉的人用的
直接來講如何 AC。
從 NA 40% 的解法可以知道:只差 的轉移式就 AC 了。
不難理解,其實 的轉移式就是把 的轉移式相加
把他們分開就好。
因此,所有 轉移式如下:
code_直觀
code_稍微包裝
D. 簡單的奇偶問題
觀念
這題有三個重點奇偶數的性質、位元運算和p的範圍。
- 奇偶數的性質: (、)
- 位元運算: (
xor
的性質)
- ^
- ^
- ^
- ^
- p的範圍: ,而
int
能儲存的最大值為 ,代表有 個位元可以使用
觀察完以上三點性質後就會發現,
如果以 當成偶數, 當成奇數,
把每一組數列儲存到一個 int
裡面,
並將兩者取 xor
就可以達到奇偶數相加判斷的效果。
實作
這題因為是區間更新,
所以要處理一下 時的 xor
要怎麼處理。
其實使要記得一個性質 ^^,
也就是只要該數字被 xor
偶數次,
那可以視為沒有被運算過,
所以程式碼可以被這樣實作
這時還有另一個問題,
當在查詢時,
如果發生了 ql > r || l > qr
的情況,
那要回傳甚麼?
這個問題有兩個解法
- 直接寫一個新的資料結構
- 再次利用位元運算的性質
針對第一種解法,
其實就是在加上一個布林值來判斷是不是從出界的遞迴的回傳值。
所實作的資料結構如下
code
第二種解法可以觀察一下位元運算,
- ^
- ^
可以看到不管什麼數字 xor
還是原本的數字,
所以可以直接回傳 。
整體的程式碼的實作如下
solution1
#include <bits/stdc++.h>
using namespace std;
template<class T> struct Num {
bool 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';
}
}
}
solution2
#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%
照著題敘一個一個把人砍掉即可
code
AC
solve 1
其實砍人有其規律:
如果把編號 砍完一圈算一輪,
假設現在是第 輪,還有 人未蹲下,且未蹲下的人中,編號最小的是 ,則:
- 未蹲下的人中,相鄰編號差距為
- 從編號 砍完,人會剩 人
- 若 為奇數,則砍完人後,最小編號會變成
如此就可以省去 for
迴圈從 跑到 的過程。
有趣的是,如果 用 pow
的話,不僅慢,還可能有精度問題 (他是浮點數)
因此我們可以把 次方建出來,加快速度。
(由於 及第 3. 點證明,因此可以得知 )
code
solve 2
如果你花了很久的時間找數列的規律,
可以發現答案是:
可以轉換成數學式:
code
F. 么棟陸砲彈
AC
簡化這題題目:給定一陣列,有區間修改和單點查值的操作,輸出查值。
這題既然是區間修改和單點查值,顯然是 BIT
水題
code
G. Gura's Factory
觀念
這題的重點就是如何處理
這串看起來頗複雜的數學式子。
因為造成數學式子複雜的原因是絕對值,
我們就先討論並分解,
討論時要注意 和 的條件。
分解完就會發現只要判斷 和 之間的關係不會影響答案,
只有 為最大或最小會影響,
因此可以將 、 的部分做預處理,
再利用線段樹針對 的大小做查詢就會找到答案。
預處理
其實就是窮舉 所有的可能,
並針對每一種可能對 做查詢。
code
實作
跟預處理的概念一模一樣,
只是這次會給定 的值,
所以只要針對該值進行查詢就好。
Time Complexity
:
solution1
#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];
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';
}
}
實作到這裡會突然意識到一件事,
因為這題根本不需要修改,
所以根本不用用到線段樹
Image Not Showing
Possible Reasons
- The image file may be corrupted
- The server hosting the image is unavailable
- The image path is incorrect
- The image format is not supported
Learn More →
,
直接用單調列隊就可以解了。Time Complexity
:
solution2
H. 神奇的卡牌遊戲
NA 55%
比賽的時候,如果你看到單筆測資,判斷又只有少數個的話,在競賽容許的情況下,你可以直接丟判斷,或是用random
跑判斷,可以喇到一些分數。
NA 90%
喇完55%後可以發現後面的幾乎都是NO
,所以你可以再加個判斷
AC
這次單純是題目出爛,還是要學一下正解怎麼做的
Image Not Showing
Possible Reasons
- The image file may be corrupted
- The server hosting the image is unavailable
- The image path is incorrect
- The image format is not supported
Learn More →
如果你有仔細看題目的話你會發現複雜度
的地方很奇怪
再仔細想想的話你會發現,如果前n-1
個卡牌都已經決定好是否翻了,若第n-k
個卡牌反面的話你就必須要翻第n
個卡牌。
所以你只要對前k
個卡牌的是否翻的可能性都做一遍,之後就可以不斷地維護左界。
code
複雜度 :