## 薄荷巧克力 (Icecream)
白話一點,就是找出有哪些數字有出現在$b$中但沒有出現在$a$中。因為題目有說$a_i,b_i\le 10^3$所以可以各開一個$10^3$的陣列紀錄個數字有沒有出現過。
有人想成圖論,如果對於每組$a_i,b_i$視為一條從$a_i\rightarrow b_i$的邊,那就是找出度為$0$的所有節點。
時間複雜度$O(n+sz)$
($sz=10^3$)
### AC code
:::spoiler AC code
```cpp=
#include <bits/stdc++.h>
using namespace std;
signed main() { cin.tie(0) -> sync_with_stdio(0);
int n, sz = 1e3 + 5;
cin >> n;
vector<bool> a(sz), b(sz);
for(int i = 0; i < n; ++i) {
int x, y;
cin >> x >> y;
a[x] = b[y] = true;
}
for(int i = 0; i < sz; ++i) {
if(b[i] && !a[i]) cout << i << ' ';
}
}
```
:::
## 數字轉換 (Password)
實作題,照著題目要求就可以AC了
本來評估實作時間為30-40分鐘,預計是你們都可以拿到,但好像有點慘
話說某人的AC code只有不到30行,你們還不AC是在幹嘛
### AC code
by chrislaiisme
:::spoiler AC code
```cpp=
#include<bits/stdc++.h>
#define LL loli
#define loli long long
using namespace std;
template<typename T> using Vec = vector<T>;
template<typename T> using Mat = Vec<Vec<T> >;
LL op;
string S;
Mat<LL> mat(4,Vec<LL>(4)), mat2[2] = {
{{1,0,0,0},{0,1,0,0},{0,0,1,0},{0,0,0,1}},
{{2,1,1,3},{3,2,1,1},{1,3,2,1},{1,1,3,2}}
};
int main() {
cin >> op >> S;
for(LL i=0; i<4; i++) for(LL j=0; j<4; j++) mat[i][j] = stoi(S.substr((i*4+j)*2, 2));
for(LL T=0; T<10; T++) {
for(LL i=0; i<4; i++) for(LL t=0; t<i; t++) for(LL k=0; k<3; k++) swap(mat[i][k], mat[i][k+1]);
Mat<LL> tmp(4,Vec<LL>(4,0));
for(LL i=0; i<4; i++) for(LL j=0; j<4; j++) for(LL k=0; k<4; k++) tmp[i][j] = (tmp[i][j] + mat2[op-1][i][k]*mat[k][j])%64;
mat = tmp;
}
string str = "";
for(char chr='A'; chr<='Z'; chr++) str += chr;
for(char chr='a'; chr<='z'; chr++) str += chr;
for(char chr='0'; chr<='9'; chr++) str += chr;
str += '+', str += '/';
for(auto &i : mat) for(auto &j : i) cout << str[j]; cout << endl;
}
```
:::
## 怪物遊戲 (Monster)
首先可以有一個很直覺的greedy想法是將所有怪物對於$a_i$由小排到大(令排序完後怪物等級為陣列$a$,獲得等級為陣列$b$)
可以觀察到如果初始等級越大,能擊敗的怪物就越多,但是題目給定初始等級,而不是打倒幾隻怪物的最小初始等級
這時候可以有一個想法是開一個陣列(令陣列名稱為$dp$)預處理打倒$i$隻怪物所需要的最小等級,可以發現
$$
dp[i] =
\begin{cases}
b[0], & i = 0 \\
\min(dp[i-1], a[i]-\sum_{j=0}^{i-1}b_i), & else
\end{cases}
$$
之後對$dp$陣列用二分搜(std::upper_bound)出$L_j$能打倒幾隻怪物即可
時間複雜度:$O(nlogn+qlogn)$
### AC code
:::spoiler AC code
```cpp=
#include<bits/stdc++.h>
using namespace std;
#define IO ios::sync_with_stdio(false),cin.tie(0)
#define F first
#define S second
#define pii pair<int,int>
#define all(v) (v).begin(),(v).end()
#define int long long
void solve(){
int n,q;cin>>n>>q;
vector<pii>v(n);
for(auto &i:v) cin>>i.F;
for(auto &i:v) cin>>i.S;
sort(all(v));
vector<int>dp(n);
dp[0]=v[0].F;
int cnt=v[0].S;
for(int i=1;i<n;i++){
dp[i]=max(dp[i-1],v[i].F-cnt);
cnt+=v[i].S;
}
while(q--){
int x;cin>>x;
cout<<upper_bound(all(dp),x)-dp.begin()<<'\n';
}
}
signed main(){
// cin.exceptions(cin.failbit);
IO;
int tt=1;
// cin>>tt;
while(tt--){
solve();
}
}
```
:::
## 移動數字 (Number)
這題最初想到的時候感覺不難,出題者自己寫一寫發現好像不太對(那個移動怎麼要那麼長啊啊啊雖然都是複製貼上但還是好長啊啊啊),最終考試裡不意外地完全沒人碰。
(只看行數,原來這題才是實作題嗎)
$40\%$解:用$\text{BFS}$,每種狀態當成一個節點,紀錄空格的位置,每次轉移(移動木塊)就是推入新的節點。要記錄有沒有遇過的$\text{bool}$陣列要用$\text{map}$。
令$n=3$,則我的寫法時間複雜度應該是$O((n^2)!\times n^2)$
$(n^2)!$是最差情況下,每種可能性都跑過;再乘以$n^2$是判斷是否到達終點。
註:有電神提出用雙向$\text{BFS}$,但我不會 :poop:
$100\%$解:注意到棋盤上只有九個一位數字,所以我們可以用一個$\text{long long}$變數代表棋盤。這樣的話常數和複雜度都會變小。
$\text{AC code}$中的$\text{getBit}$是提取出第$p$位數字,$\text{swapBit}$是交換第$z$和第$tar$個$\text{bit}$。至於裡面的$\text{auto}$是$\text{lambda function}$,有興趣的可以自行學習。
### code
#### 陣列版(40%)
若結果為-1,約需7500ms
:::spoiler 陣列版
```cpp=
#include <bits/stdc++.h>
//#include <ctime>
using namespace std;
#define pii pair<int, int>
#define f first
#define s second
//clock_t tStart, tEnd;
signed main() { cin.tie(0) -> sync_with_stdio(0);
vector<vector<int>> board(3, vector<int>(3));
pii zero; // position of blank square
for(int i = 0; i < 3; ++i) {
for(int j = 0; j < 3; ++j) {
cin >> board[i][j];
if(board[i][j] == 0) zero = make_pair(i, j);
}
}
//tStart = clock();
vector<vector<int>> goal = {{1, 2, 3}, {4, 5, 6}, {7, 8, 0}};
map<vector<vector<int>>, bool> vst;
queue<pair<pair<vector<vector<int>>, pii>, int>> q;
// {{gameBoard, zeroPos}, steps}
q.push({{board, zero}, 0});
vst[board] = true;
while(!q.empty()) {
auto cur = q.front();
q.pop();
if(cur.f.f == goal) {
cout << cur.s;
return 0;
}
vector<vector<int>> tmp = cur.f.f;
int a = cur.f.s.f, b = cur.f.s.s;
// push 1 square
vector<int> dx = {-1, 0, 0, 1}, dy = {0, -1, 1, 0};
for(int i = 0; i < 4; ++i) {
int x = a + dx[i], y = b + dy[i];
if(x >= 0 && x < 3 && y >= 0 && y < 3) {
swap(tmp[a][b], tmp[x][y]);
if(!vst[tmp]) q.push({{tmp, {x, y}}, cur.s + 1}), vst[tmp] = true;
swap(tmp[a][b], tmp[x][y]);
}
}
// push 2 squares
if(a == 0) {
swap(tmp[0][b], tmp[1][b]);
swap(tmp[1][b], tmp[2][b]);
if(!vst[tmp]) q.push({{tmp, {2, b}}, cur.s + 1}), vst[tmp] = true;
swap(tmp[1][b], tmp[2][b]);
swap(tmp[0][b], tmp[1][b]);
}
if(a == 2) {
swap(tmp[1][b], tmp[2][b]);
swap(tmp[0][b], tmp[1][b]);
if(!vst[tmp]) q.push({{tmp, {0, b}}, cur.s + 1}), vst[tmp] = true;
swap(tmp[0][b], tmp[1][b]);
swap(tmp[1][b], tmp[2][b]);
}
if(b == 0) {
swap(tmp[a][0], tmp[a][1]);
swap(tmp[a][1], tmp[a][2]);
if(!vst[tmp]) q.push({{tmp, {a, 2}}, cur.s + 1}), vst[tmp] = true;
swap(tmp[a][1], tmp[a][2]);
swap(tmp[a][0], tmp[a][1]);
}
if(b == 2) {
swap(tmp[a][1], tmp[a][2]);
swap(tmp[a][0], tmp[a][1]);
if(!vst[tmp]) q.push({{tmp, {a, 0}}, cur.s + 1}), vst[tmp] = true;
swap(tmp[a][0], tmp[a][1]);
swap(tmp[a][1], tmp[a][2]);
}
}
cout << -1;
//tEnd = clock();
//cout << "\nrun time: " << tEnd - tStart << "ms";
}
```
:::
#### 狀壓版(100%)
若結果為-1,約需120ms
:::spoiler 狀壓版
```cpp=
#include <bits/stdc++.h>
//#include <ctime>
using namespace std;
#define ll long long
#define pii pair<int, int>
#define f first
#define s second
//clock_t tStart, tEnd;
signed main() { cin.tie(0) -> sync_with_stdio(0);
ll board = 0, zero;
for(int i = 0; i < 9; ++i) {
int k;
cin >> k;
board = 10 * board + k;
if(k == 0) zero = 8 - i;
}
//tStart = clock();
vector<ll> pow10 = {1};
for(int i = 0; i < 10; ++i) pow10.push_back(pow10.back() * 10);
auto getBit = [&pow10](ll &n, int &p) {
return n / pow10[p] % 10;
};
auto swapBit = [&getBit, &pow10](ll &n, int z, int tar) {
int k = getBit(n, tar), l = getBit(n, z);
n -= k * pow10[tar], n += k * pow10[z];
n -= l * pow10[z], n += l * pow10[tar];
};
ll goal = 123456780;
unordered_map<ll, bool> vst;
queue<pair<pair<ll, int>, int>> q;
// {{board, zeroPos}, steps}
q.push({{board, zero}, 0});
while(!q.empty()) {
auto pt = q.front();
q.pop();
ll cur = pt.f.f;
int z = pt.f.s, step = pt.s;
if(cur == goal) {
cout << step;
return 0;
}
if(z + 3 < 9) {
swapBit(cur, z, z + 3);
if(!vst[cur]) q.push({{cur, z + 3}, step + 1}), vst[cur] = true;
swapBit(cur, z, z + 3);
}
if(z - 3 >= 0) {
swapBit(cur, z, z - 3);
if(!vst[cur]) q.push({{cur, z - 3}, step + 1}), vst[cur] = true;
swapBit(cur, z, z - 3);
}
if(z / 3 == 0) {
swapBit(cur, z, z + 3);
swapBit(cur, z + 3, z + 6);
if(!vst[cur]) q.push({{cur, z + 6}, step + 1}), vst[cur] = true;
swapBit(cur, z + 3, z + 6);
swapBit(cur, z, z + 3);
}
if(z / 3 == 2) {
swapBit(cur, z, z - 3);
swapBit(cur, z - 3, z - 6);
if(!vst[cur]) q.push({{cur, z - 6}, step + 1}), vst[cur] = true;
swapBit(cur, z - 3, z - 6);
swapBit(cur, z, z - 3);
}
if(z % 3 > 0) {
swapBit(cur, z, z - 1);
if(!vst[cur]) q.push({{cur, z - 1}, step + 1}), vst[cur] = true;
swapBit(cur, z, z - 1);
}
if(z % 3 < 2) {
swapBit(cur, z, z + 1);
if(!vst[cur]) q.push({{cur, z + 1}, step + 1}), vst[cur] = true;
swapBit(cur, z, z + 1);
}
if(z % 3 == 0) {
swapBit(cur, z, z + 1);
swapBit(cur, z + 1, z + 2);
if(!vst[cur]) q.push({{cur, z + 2}, step + 1}), vst[cur] = true;
swapBit(cur, z + 1, z + 2);
swapBit(cur, z, z + 1);
}
if(z % 3 == 2) {
swapBit(cur, z, z - 1);
swapBit(cur, z - 1, z - 2);
if(!vst[cur]) q.push({{cur, z - 2}, step + 1}), vst[cur] = true;
swapBit(cur, z - 1, z - 2);
swapBit(cur, z, z - 1);
}
}
cout << -1;
//tEnd = clock();
//cout << "\nrun time: " << tEnd - tStart << "ms";
}
```
:::
## 一起走過的路 (Path)
note:第一個子測資只要輸出$0$就能拿到1分
我們可以先預處理從節點$1$走到節點$i(1\leq i \leq n)$所需時間
對於數對$(i,j),1\leq i< j\leq n$可以發現兩人分離的地方恰好是節點$i$與$j$的最近共同祖先,但若是出所有節點$i$與$j$求出最近共同祖先再一個一個加總答案,時間複雜度會是$O(n^2logn)$,會吃TLE
我們可以換另一個角度思考題目:對於所有數對$(i,j)$,節點$i$與$j$的最近共同祖先為節點$node$的數量有多少
於是,答案為
$$
\sum{}^{}最近共同祖先為節點node的次數 \times 從節點1走到節點node所需時間
$$
若是將節點$node$拔除,對於所有數對$(i,j)$,節點$i$與$j$在需要不同的樹中;或是一個節點為$node$,另一節點為以$node$為根的子樹,兩節點的最近共同祖先才會是$node$
這時候需要用排列組合,最近共同祖先為節點node的次數為「任兩子樹大小相乘後的加總」,但此一作法最差為$O(n^2)$,所以要改用另一計算方法為「以$node$為子樹中所有節點$i$與$j$中所有可能數量,減去節點$i$與$j$在同一子樹中的可能」
時間複雜度:$O(n)$
### AC code
記得 $mod\ 1000000007$
:::spoiler AC code
```cpp=
#include<bits/stdc++.h>
using namespace std;
#define IO ios::sync_with_stdio(false),cin.tie(0)
#define F first
#define S second
#define pii pair<int,int>
#define int long long
const int mod=1e9+7;
const int inf=9e18;
void dfs(vector<vector<pii>>&v,vector<int>&sbtsz,vector<int>&weight,int n){
for(auto i:v[n]){
weight[i.F]+=weight[n]+i.S;
dfs(v,sbtsz,weight,i.F);
sbtsz[n]+=sbtsz[i.F];
}
}
void solve(){
int n;cin>>n;
vector<int>a(n-1),b(n-1);
for(auto &i:a) cin>>i;
for(auto &i:b) cin>>i;
vector<vector<pii>>v(n+1);
for(int i=2;i<=n;i++){
v[a[i-2]].push_back({i,b[i-2]});
}
vector<int>sbtsz(n+1,1),weight(n+1);
dfs(v,sbtsz,weight,1);
int ans=0;
for(int i=1;i<=n;i++){
int cnt=sbtsz[i]-1;
cnt=cnt*(cnt-1)>>1;
for(auto j:v[i]){
cnt=cnt-(sbtsz[j.F]*(sbtsz[j.F]-1)>>1);
}
cnt+=sbtsz[i]-1;
ans=(ans+(((weight[i]%mod)*(cnt%mod))%mod)%mod);
}
cout<<ans%mod<<'\n';
}
signed main(){
// cin.exceptions(cin.failbit);
IO;
int tt=1;
// cin>>tt;
while(tt--){
solve();
}
}
```
:::
## 後記
出題者一覽:
薄荷巧克力 (Icecream):Boling
數字轉換 (Password):mattwu
怪物遊戲 (Monster):Vandrin
移動數字 (Number):Boling
一起走過的路 (Path):Vandrin
感謝william1010121的judge架設及chrislaiisme的驗題,所有人都幫了Vandrin很多忙
對於第二題與第三題題序有誤,十分抱歉
這一次的子任務沒有很多,若未來還有機會出題應該會增加子任務
在第三題有些$O(nq)$解可以通過子任務2,可能是常數太小通過(DDJ也會過),但是所有submissions結果皆以提交在cms競賽中的結果為主
雖然這次題目有大概按照難度排序,但是很多競賽其實都不太會對題目難度做排序,在沒有計分板的情況下,建議在每次競賽先確保了解所有題目題意後再決定作答