---
# System prepended metadata

title: S3 演算法期中考題解
tags: [S3 演算法課程]

---

# S3 演算法期中考題解
###### tags: `S3 演算法課程`

## A. DNA 比對 ( DNA )

出題：fishhh

:::spoiler 賽中參考解答
其實直接做就AC了
```cpp=
#include "iostream"
using namespace std;
int main(){
    string a,b;
    cin>>a>>b;
    int q;
    cin>>q;
    while(q--){
        int l,r;
        cin>>l>>r;
        bool faild=0;
        l--,r--;
        for(int i=l;i<=r;i++){
            if(a[i]!=b[i]){
                faild=1;
                break;
            }
        }
        if(faild){
            cout<<"I am so weak.\n";
        }
        else{
            cout<<"Really?!\n";
        }
    }
}
```
:::
<br/>

:::spoiler 出爛了

1. 我沒有想到可以用 substr 寫 QQ

2. 也是因為大家分數都太低，最後賽中臨時決定把它變成水題。

3. 原本有另外一個版本(解法都相同)
如果出這樣是不是就沒辦法 substr 了XD
![](https://hackmd.io/_uploads/Ski7XLb6i.png)


於是後來出完全相同的版本 燒雞
:::

<br/>

::: spoiler 正解

* 子任務 1 暴力做，第一時間會想到的作法

* 子任務 2 建表：建一個 $ary[i][j]$ 如果是 $1$ 代表兩個字串 $s_i~s_j$ 完全相同。

> 在講子任務 3 之前先來討論一下複雜度!
字串最大長度 $10^5$ 詢問次數最大 $2\times10^5$ 每次詢問最多可以從 $1$ 問到 $n$ 所以每次詢問最多需要跑 $10^5$。
如果暴力，就會是 $O(nq)$ 就是接近 $10^{10}$ 顯然TLE。

* 子任務 3 前綴合
當 $s_{1_i}==s_{2_i}$ 那就建立一個ary 紀錄為 1 其他則為 0。

可以發現一件事，當一個區間字串完全相同時，這個區間ary的合就會剛好是區間的長度!
(好像只有一個人做出正確解法)

||@鯨魚島麻糬#5110 在pA砸了線段樹 毒瘤!||

```cpp=
#include "iostream"
using namespace std;
int pre[100010]={};
int main(){
    ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
    string s1,s2;
    cin>>s1>>s2;
    for(int i=0;i<s1.size();i++){
        pre[i+1]=pre[i];
        if(s1[i]==s2[i]){
            pre[i+1]+=1;
        }
    }
    int q,l,r;
    cin>>q;
    while(q--){
        cin>>l>>r;
        if(pre[r]-pre[l-1]==r-l+1){
            cout<<"Really?!\n";
        }
        else{
            cout<<"I am so weak.\n";
        }
    }
}
```

:::





## B. YTP 的記分板（YTP Scoreboard）

出題：Koying

:::spoiler 子任務 1：沒有 AC
維護一個二維陣列，每次都將對應位置 -1 即可

:::

:::spoiler 滿分解
需要注意兩個細節：
1. 當該題 AC 時需要有正號
2. 0 可能有 0、+0 兩種情況（沒上傳過 or 一發 AC）
3. AC 過了之後記分板不會再改變

我的作法：AC 時再 +1（意思是兩次 WA 之後 AC 會存 $3$），最後輸出時再 -1 即可
:::

:::spoiler 參考程式：
```cpp=
#include <bits/stdc++.h>

using namespace std;

#define MAXN 105

int n, m, p;
string name[MAXN];
int score[MAXN][MAXN];

void print() {
    for (int i = 0; i < n; i++) {
        cout << name[i] << ' ';
        for (int j = 0; j < p; j++) {
            cout << (score[i][j] > 0 ? "+" + to_string(score[i][j] - 1) : to_string(score[i][j])) << ' ';
        }
        cout << '\n';
    }
}

void update(string user, int p, string res) {
    --p;
    for (int i = 0; i < n; i++) {
        if (name[i] == user) {
            if (res == "AC" && score[i][p] <= 0) {
                score[i][p] = abs(score[i][p]) + 1;
            }
            else if (score[i][p] <= 0) {
                score[i][p] -= 1;
            }
            break;
        }
    }
}

signed main() {
    ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    cin >> n >> m >> p;

    for (int i = 0; i < n; i++)
        cin >> name[i];
    
    string op, res;
    int p;
    while (m--) {
        cin >> op;

        if (op == "view") {
            print();
        }
        else {
            cin >> p >> res;
            update(op, p, res);
        }
    }
} 
```
:::

## C. 神奇的環狀數列 ( Circle )

出題：fisl℩l℩

:::spoiler 題解
> 前綴和前綴和前綴和!!

* 子任務 1：直接實作即可 複雜度：$O(k)$ 顯然在 $k$ 接近 $10^9$ 顯然TLE。
<br/>
* 子任務 2：發現到不管從哪個地方開始 都會先走 $\lfloor k/n \rfloor$ 再回到出發點。所以可以先做掉 然後再走剩下的就好。<br/>但起點還是要窮舉一次，所以每次會跑 $n \times (k\%n)$ 考慮到最糟情況  $k \% n = n-1$ 時複雜度就會是 $O(n^2)$。 當 $(10^5)^2$ 顯然也會TLE。
<br/>
* 子任務 3：可以發現一件事情，在最後窮舉起點的時候，需要去求區間 $[i,i+(k\%n)]$ 的合。說到區間合，那麼前綴合$o(n)$ 建表 $O(1)$ 查詢再好不過了! 整題複雜度 $O(n)$ 超漂亮的><
<br/>
```cpp=
#include "iostream"
using namespace std;
#define int long long
int ary[400010]={};
signed main(){
    ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
    int n,k;
    cin>>n>>k;
    for(int i=1;i<=n;i++){
        cin>>ary[i];
    }
    int base=0;
    int cir=k/n;
    for(int i=1;i<=n;i++){
        int nxt=max((int)1,ary[i]-cir+1);
        base+=(ary[i]+nxt)*(ary[i]-nxt+1)/2;
        nxt--;
        ary[i]=nxt;
        ary[i+n]=nxt;
    }
    for(int i=1;i<=2*n;i++){
        ary[i]+=ary[i-1];
    }
    int ans=0;
    k-=cir*n;
    for(int i=1;i<=n;i++){
        ans=max(ans,ary[i+k]-ary[i]);
    }
    cout<<ans+base<<"\n";
}
```
:::



## D. 這是一個佇列問題（Queue）

出題：Koying

:::spoiler 子任務 1、3：
你只要會 stack or queue，然後暴力做就可以了
:::

:::spoiler 子任務 2、4：
利用兩個 queue or stack，一個紀錄字元，一個紀錄數量，每次操作只會 push 一次

均攤複雜度：$\mathcal{O}(n)$
:::

:::spoiler 滿分解：
需要同時用到 stack、queue，所以我們需要 deque

用陣列實作或是直接用 `std::deque` 都可以
:::

:::spoiler 參考解答：
```cpp=
#include <bits/stdc++.h>

using namespace std;

#define MAXN 200005

#define int long long

int n, x;
char c;
char s[MAXN];
int cnt[MAXN];

void print(int l, int r) {
    int sum = cnt[l];
    char ch = s[l];

    for (int i = l + 1; i < r; i++) {
        if (s[i] == ch)
            sum += cnt[i];
        else {
            cout << sum << ch;
            sum = cnt[i];
            ch = s[i];
        }
    }

    cout << sum << ch << '\n';
}

signed main() {
    ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    cin >> n;
    int l = n, r = n, op;
    while (n--) {
        cin >> op;
        if (op == 5)
            print(l, r);

        if (op == 1) {
            cin >> x >> c;
            s[r] = c;
            cnt[r++] = x;
        }
        if (op == 2) {
            cin >> x >> c;
            s[--l] = c;
            cnt[l] = x;
        }
        if (op == 3) {
            cin >> x;
            int sum = 0;
            for (int i = r - 1; i >= l; i--) {
                sum += cnt[i];
                if (sum >= x) {
                    if (sum == x) {
                        r = i;
                        break;
                    }
                    else {
                        cnt[i] -= x - (sum - cnt[i]);
                        r = i + 1;
                        break;
                    }
                }
            }
        }

        if (op == 4) {
            cin >> x;
            int sum = 0;
            for (int i = l; i < r; i++) {
                sum += cnt[i];
                if (sum >= x) {
                    if (sum == x) {
                        l = i + 1;
                        break;
                    }
                    else {
                        cnt[i] -= x - (sum - cnt[i]);
                        l = i;
                        break;
                    }
                }
            }
        }
    }
}
```
:::

## E. 字串反轉 ( Reverse )

出題：f1sh

:::spoiler 題解
* 子任務 1 就直接實作即可~
* 子任務 2 有兩種做法其中一個是迴圈一個是遞迴

這裡分享一下遞迴的做法：
```cpp=
#include "iostream"
using namespace std;
string s;
int ary[2000]={},ed=0;
void push(int num){
	ary[ed++]=num;
	return ;
}
void reverse(int beg,int end){
	for(int i=beg,j=end;i<j;i++,j--){
		swap(ary[i],ary[j]);
	}
	return ;
}
int recur(int pos,int bg,int inl){ //return endpoint
	int i;
	for(i=pos;i<s.size();i++){
		if(s[i]=='['){
			i=recur(i+2,ed,1);
		}
		else if(s[i]==']'){
			i++;
			break;
		}
		else{
			int ret=0;
			while (s[i]==' '){
				i++;
			}
			
			while(i<s.size()&&s[i]!=' '){
				ret*=10;
				ret+=s[i]-'0';
				i++;
			}
			push(ret);
		}
	}
	if(inl)reverse(bg,ed-1);
	return i;
}
int main(){
    int n;
    getline(cin,s);
	getline(cin,s);
	s+=' ';
	recur(0,0,0);
	for(int i=0;i<ed;i++)cout<<ary[i]<<' ';
	cout<<"\n";
}
```

下面是 korzying 提供的迴圈做法

```cpp=
#include <bits/stdc++.h>

using namespace std;

#define MAXN 2005

string s[MAXN];
int pos[MAXN];

signed main() {
    // I am so dian
    ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    string input;
    int nowsize = 0;
    int leftcnt = 0;
    while (cin >> input) {
        s[nowsize++] = input;

        if (input == "]") {
            for (int i = nowsize - 1; i >= 0; i--) {
                if (s[i] == "[") {
                    for (int j = i + 1; j < (nowsize - 1) - (j - (i + 1)); j++) {
                        swap(s[j], s[(nowsize - 1) - (j - i)]);
                    }
                    for (int j = i; j < nowsize - 1; j++) {
                        swap(s[j], s[j + 1]);
                    }
                    nowsize -= 2;
                    break;
                }
            }
        }
    }

    for (int i = 0; i < nowsize; i++) {
        cout << s[i] << ' ';
    }
    cout << endl;


}
```
:::