---
# System prepended metadata

title: 考幹考題解 Part 1(?) by yungyao

---

# 考幹考題解 Part 1(?) by yungyao
###### tags: 題解 考幹

只有標題裡面是"XX和她/他的XXXX"是我出的ㄛ
其他題的題解請找其他出題人(雖然好像是我沒寫完，是別人要來找我咩噗
\學弟妹特別強/

## A部分
這部分都語法題
個人是希望每個人都能把這裡的500分都拿到
希望我在這裡講題解的時候大家都有做到><（結果我根本還沒寫完題解，慘

### pA3
tags : `string` `implementation` `constructive algorithm`
就是基本的回文性質跟簡單的字串相關語法
理論上分兩個Subtask，也就是25分解跟100分解

但cf不給連續給分，所以可能會有各種奇怪ㄉ分數（像py被卡常之類的www
總之我們只要考慮一個簡單的greedy就好

對於任意一組 $s[i] \neq s[n-i+1]$ ，我們都修改其中任意一項
透過迴文的性質可以很容易的證明做這個改動是好的greedy策略

所以我們只要跑過去數有幾個相異就好了
而滿分解要你輸出改過的字串，也是直接改一下就好了w

:::spoiler AC Code
```cpp = 1
using namespace std;
#include <iostream>

int main(){
    int n;
    string s;

    cin >> n >> s;
    int cnt = 0;
    for (int i=0;i<n/2;++i){
        if (s[i] != s[n-i-1]){
            ++cnt;
            s[n-i-1] = s[i];
        }
    }

    cout << cnt << '\n' << s;

    return 0;
}
```
:::


### pA4 浩浩和他的夢想柯基
tags : `stl` `queue` `data structure`
這題的定位是裸queue
算是給有在上上學期大社的人的均標，寫不出來代表你太混惹QQ
（Subtask 1應該算低標ㄛ

因此對於這題你基本上只需要知道一件事
就是`std::queue` 裡面可以放任何C++的element，而不限於variable
或者你要自己刻一個queue也是可以接受的啦

#### Subtask 1 (31%)
這個Subtask因為保證每隻柯基的名字都一樣，也就是說你只需要記錄第一隻柯基的名字
再來只要記錄收容所裡面有幾隻柯基就好了

所以31分只要這樣就能拿了

:::spoiler 31 points
```cpp
int main(){
    int siz = 0;
    int t;
    string corgi;
 
    cin >> t;
    while (t--){
        char cmd;
 
        cin >> cmd;
        if (cmd == '1'){
            cin >> corgi;
            ++siz;
        }
        else if (cmd == '2'){
            int adp;
            cin >> adp;
 
            if (!adp && siz){
                cout << corgi << '\n';
                --siz;
            }
        }
    }
 
    return 0;
}
```
:::

#### Subtask 2,3,4
這三個Subtask基本上一樣
只是對於有些操作需要注意不同的事項，所以可以一起講

反正就是要維護一個queue資料結構


##### Subtask 2
這個Subtask保證每次對`queue`做`pop`時`queue`都不為空，所以你可以不用檢查是否`empty`

##### Subtask 3
這裡則保證每一次`pop`都可以輸出東西，其實我不知道什麼情況下會寫爛這件事，大概沒判斷好之類的吧，相信拿到這個Subtask的人，其他應該也不難拿XD

##### Subtask 4
就不保證以上兩個Subtask的事，但我相信你們做出這兩個Subtask應該也不難做滿分解啦

:::spoiler 100 points yea
```cpp
int main(){
    ios_base::sync_with_stdio(false),cin.tie(0);
    int t;
    queue <string> Q;

    cin >> t;
    while (t--){
        int cmd;

        cin >> cmd;
        if (cmd == 1){
            string s;

            cin >> s;
            Q.push(s);
        }
        else if (cmd == 2){
            int k;

            cin >> k;
            if (Q.size()){
                if (!k){
                    cout << Q.front() << '\n';
                }
                Q.pop();
            }
        }
    }

    return 0;
}
```
:::

## B部分

這裡會稍難一點，但我相信每個人多少都能拿到一些部分分，有來演算法小社跟算法組的學弟妹應該要至少拿一半的分數，加油ㄛ

### pB1 浩浩和果酒和他們的執秘身高差
tags : `double pointer` `binary-search`
先說一下這題的小故事好了，雖然你們最後沒參加到寒訓
但在籌備過程中，某很電的沉思緯學長一直想知道浩浩跟果酒誰比較高
然後我也是十分之有興趣，但他們每次都會避開站在一起，所以我們一直不知道到底誰比較高
直到某次社遊我跟#yououorz在討論題目的時候討論到[TIOJ 1905 最理想的身高差](https://tioj.ck.tp.edu.tw/problems/1905)

剛好那天我們也是努力的想要知道誰比較高，我就想到可以把那題給簡化一下（其實簡化滿多ㄉ，我到現在還不會寫原題XD）當作你們考幹題，於是就有ㄌ這題

還有就是我為了在最後一筆Subtask卡掉`std::set`，所以$n$給到很大，原本是只打算給到$2\times10^5$ㄉ
反正`std::set`跟`std::map`都是很肥的資結，題目想要搞事~~（像是我）~~就會在這邊卡常（之前寫poj的題目ㄉ時候被卡set大腸懷恨在心辣

#### Subtask 1
這裡保證$0 \leq a_i,b_i \leq 400$，換句話說答案也必定小於400

至於要如何檢查一個數$k$能不能是答案，只要檢查$a_i + k \in b$或$a_i - k \in b$，而這件事可以用一個`bool`陣列或`std::bitset`去存一個值是否被包含在$b$之中，反正都會是$O(1)$
複雜度$O(nC)$ (btw其實有開放$O(nC^2)$，因為我懶得卡，而且其實想法差不多XD)

#### Subtask 2
這個Subtask跟前一個很像，只是$n,a_i,b_i$都變大了，$O(nC)$會過肥

但你只需要做一個小小的優化，就是對於$a$也做跟$b$相同的事
用一個`bool`陣列或`std::bitset`存$a$，就不用對$a$枚舉了，現在只需要對$a$的值域枚舉

複雜度可以降到$O(C^2)$

#### Subtask 3
因為$n \leq 2000$，也就是$O(n^2)$可過，而你會發現差一共也只有$n^2$個，因此你只要兩層`for`迴圈包起來就能拿到分數ㄌ

:::spoiler 枚舉亂做拿分數，建北電資北市賽有望
```cpp
LL a[3000100];

int main(){
    ios_base::sync_with_stdio(false),cin.tie(0);
    int n;
    LL ans = 1e10;

    cin >> n;
    for (int i=n;i--;)
        cin >> a[i];
    for (int i=n;i--;){
        LL x;
        cin >> x;
        for (int j=n;j--;)
            ans = min(ans,abs(x-a[j]));
    }

    cout << ans;

    return 0;
}
```
:::


但要特別注意的是，最大的差會是$(2^{31}-1) - (-(2^{31}-1)) = 2^{32}-2$，會爆`int`範圍，要用`long long`去儲存。

#### Subtask 4
這應該是所有Subtask裡面最水的
你可以很輕鬆地發現在$min(a) \geq max(b)$的情況下，最小差一定會是$min(a) - max(b)$

而在$a,b$都呈非嚴格遞減下$min(a) = a_n ,max(b) = b_1$
所以你只要輸出$a_n - b_1$就好了
5分get，這5分沒拿到我應該可以相信你根本沒看題目QQ

#### Subtask 5,6
5,6基本上可以一起講，只有常數差異而已
這題的滿分解基本上有兩種，可以二分搜也可以雙指針

對於這種可以任意挑東西的題目，先sort都是一個好的想法
這題也是這樣，我們先對$a$由小到大排列

接著先考慮雙指針的作法（又稱為爬行法
:::spoiler solution 1 double pointer 我用虛擬碼寫，官解的.cpp檔長的有點醜QQ
```
int n = input()
array a = input()
array b = input()

sort (a)
sort (b)

int64 ans = 2 ^ 32 - 1
iterator b_ptr = b.begin() //對於b的指針
for element in a{
    while *next(b_ptr) < element and next(b_ptr) != b.end()
        b_ptr = next(b_ptr)
    
    ans = min (ans,abs(n - *b_ptr),abs (n - *next(b_ptr)))
}

print (ans)
```
:::

這份扣的的概念很簡單
在$a,b$都被排序下

我們對於$a$的每一項都去找到其在$b$當中介於哪兩項之間
由於$b$也已經被排序，所以我們如果維護$a$當中的每一項對應到$b$的哪一項，那這件事會遞增
所以我們就可以利用一個雙指針去好好維護他了

由於像這樣對兩個序列各維護一個遞增指針的作法，其實很像在兩個序列上爬行，所以又被稱為爬行法
但雙指針聽起來比較厲害w

然後我們可以來看一下第二種作法，二分搜的核心概念其實是一樣的
我們想要找到$b$的每一項介於$a$($a,b$反過來其實沒差)的哪兩項之間
如果我們已經將$a$排序的話，應該滿容易可以想到二分搜這個作法
基本上大同小異，差別只是實作查找這個過程而已

:::spoiler solution 2 binary search
```cpp
LL a[1500100];

int main(){
    theyRSOOOOOOOOODIAN
    int n;

    cin >> n;
    for (int i=0;i<n;++i)
        cin >> a[i];
    sort (a,a+n);
    
    LL ans = maxint64;
    for (int j=0;j<n;++j){
        LL b;

        cin >> b;
        int i = lower_bound(a,a+n,b) - a;
        if (i != n)
            ans = min(ans,a[i] - b);
        if (i)
            ans = min(ans,b - a[i-1]);
    }

    cout << ans;

    return 0;
}
```
:::

這邊順便介紹一下`lower_bound()`這個函式
他屬於標頭檔`algorithm`
他可以直接幫你在一個排序好的陣列上做二分搜
要餵給三個參數
`lower_bound ( iterator start, iterator end, k )`
分別是迭代器起始點、迭代器終點、及查找的值
前兩項類似sort餵的參數，array 的話就給他 `a,a+n`，`vector`的話就給他`a.begin(),a.end()`
而他會回傳一個指標，指向在你給他的範圍中，大於等於$k$的最小值
而如果$k$比全部的值都大，那就會回傳終點（也就是 `a+n` 或 `a.end()`）
那如果你想把他當index用，就像我前面範例code一樣，讓他剪掉起點就好了


### pB2 青蛙和他的青蛙下蛋
tags : `greedy` `bitwise`
這題是我在躺在床上睡不著的時候想到ㄉ

滿分解需要一點數學跟位元運算
不過部分分也是可以用stl做出來的，希望大家都有喇到分><

#### Subtask 1
對於$\exists k(k \in N), n = 2^k$的情形
你可以發現不管怎麼合併，最後一定都能合併成1杯
所以你只要輸出$t$次1，就得到12分ㄌ，開ㄅ開心

#### Subtask 2
那對於其他狀況我們可以考慮真的去模擬合併的過程
至於要如何好好模擬，也不是太麻煩

我們可以發現合併的過程是由小到大
而只要小的還有剩多於兩個把他們合併起來一定是好的

那我們就可以每次看最小的兩個能不能合併
可以的話就合併起來，不行就算了
理論上 $O(n^2),O(n^2logn)$ 都能過，但我也沒試就是ㄌ~~（反正沒人寫這個Subtask~~

#### Subtask 3
跟上一個Subtask類似，但我們可以優化一下找最小兩個的過程
應該滿顯然的用`priority queue`可以解決
複雜度$O(nlogn)$

:::spoiler priority queue sol
```cpp
inline void solve(){
    int n;
    priority_queue <int,vector <int>,greater <int>> pq;

    cin >> n;
    int cnt = 0;
    REP (n) pq.push(1);

    while (pq.size()){
        int pop = pq.top();
        pq.pop();

        if (pq.empty() || pop != pq.top())
            ++cnt;
        else
            pq.pop(),pq.push(pop * 2);
    }

    cout << cnt << '\n';
}

//這是我ㄉCF模版
int main(){
    ios_base::sync_with_stdio(false),cin.tie(0);
    int t;
    for (cin >> t;t--;) solve();
    return 0;
}
```
:::

#### Subtask 4
應該不難發現，最後每一杯飲料的大小都是 $2^k$
而由小到大合併後，最後會併成的杯數變會是將 $n$ 以二進位表示後的bit數量

多數人基本上都有想到這件事，接下來就是看你怎麼實作而而已了
這邊介紹一個最簡潔的實作方式
在C\++中有一系列的builtin函式，這些函式不屬於C++ Standard，但幾乎各家編譯器都有將他們實現
而且不需要引入任何標頭檔就能使用

在競賽領域中最常見的就是我們今天的主題`__builtin_popcount()`
這個函式的功能很簡單，就是回傳你給他的數有幾個bit，也就是我這題要求的東西

所以整份code可以精簡如下
:::spoiler 耶 AC
```cpp
#include <iostream>
using namespace std;
 
int main(){
	ios_base::sync_with_stdio(false); cin.tie(0);
	int t;
	for(cin >> t;t--;){
		int n;
		cin >> n;
		cout << __builtin_popcount(n) << '\n';
	}
    return 0;
}

```
:::


### pB3 湯圓和他的交換禮物
tags : `math` `dp`
數學題加打表(你要本機打也是可以啦)

簡單來說透過簡單的排組，你可以知道$n$個人的排列是$n!$種
而你可以發現一個"相同"的排法會被重複計算$n$次
所以$n$個人的相異送法數會是

$\frac{n!}{n} = (n-1)!$

其實是滿簡單的排組啦我覺得，阿反正你們下次段考就是排組，先預習一下ㄅ

接下來就是怎麼實作他了，不過這應該不難

#### Subtask 1
這個Subtask 是讓你可以用手算ㄉ
反正你就自己畫一畫就好了，也才4種輸入，最多的一組解才24
甚至已經給你兩組輸入的解ㄌ，自己畫圖ㄅw

### Subtask 2
這裡你就分別去算跑一次 $(n-1)!$
複雜度 $O(qn)$
:::spoiler 部分分
```cpp
#include <iostream>
using namespace std;

int mod = 1e9 + 7;
 
int main(){
	ios_base::sync_with_stdio(false); cin.tie(0);
	int t;
	for(cin >> t;t--;){
		int n;
		cin >> n;
        
        LL ans = 1;
        for (LL i = 2;i<n;++i)
            ans = ans * i % mod;
        cout << ans << '\n';
	}
    return 0;
}
```
:::

#### Subtask 3
這裡你會發現 $O(qn)$ 太慢了
但其實你要輸出的東西是固定的，你可以預處理完再一次輸出

這樣複雜度會是 $O(C) + O(q) = O(max(C,q))$
($C$一般是指值域)

:::spoiler AC Code
```cpp
const LL mod = 1e9+7;
LL ans[1000100];
 
int main(){
    int q;
    ans[0] = 1;
    for (int i=1;i<=1e6;++i){
        ans[i] = i * ans[i-1];
        ans[i] %= mod;
    }
 
    cin >> q;
 
    while (q--){
        int n;
 
        cin >> n;
        cout << ans[n-1] << '\n';
    }
 
    return 0;
}
```
:::

## C部分
這邊只有一題是我出的啦，寫不出來不要怪我ㄛ

### pC2 絲襪和他的模擬城市
tags : `tree` `greedy` `sort`

這題其實是抄的
原題是Codeforces 的 Goodbye 2020 contest題目，我賽中寫那題就覺得很有趣一定要拿來自己出

反正我相信你們應該沒有人打過那場啦XD

阿原本我應該要認真打題解，但我好懶
所以直接去看原題ㄅ
[Goodbye 2020 pD](https://codeforces.com/contest/1466/problem/D) [題解](https://codeforces.com/blog/entry/86126)

喔然後subtask 1只要把每個點的點權加起來輸出就好
畢竟你根本沒得分w，這個Subtask 滿多人有拿到的，讚