---
# System prepended metadata

title: APCS 2017年10月題解
tags: [APCS]

---

# APCS 2017年10月題解
## 第一題 邏輯運算子
### 題目敘述
https://zerojudge.tw/ShowProblem?problemid=c461
### 解套
N/A
### 題目分析
首先因為AND操作、OR操作和XOR操作都有對應的位運算符號，所以輸入a,b後把非0數字都轉成1會比較方便，這樣就能直接用內建的位元運算得到答案

逐一判斷，如果符合就輸出對應的字串，最後如果都不合就輸出"IMPOSSIBLE"

時間複雜度$O(1)$

### 解題流程
輸入$→$轉換$→$判斷、輸出
### 解題過程
#### 變數宣告
```cpp=3
int a,b;
bool c;
```
變數名同題目
#### 輸入、轉換
```cpp=6
cin>>a>>b>>c;
a=(a!=0?1:0),b=(b!=0?1:0);
```
#### 判斷、輸出
```cpp=8
bool cmp=0;
if((a&&b)==c){
    cmp=1;
    cout<<"AND\n";
}
if((a||b)==c){
    cmp=1;
    cout<<"OR\n";
}
if((a^b)==c){
    cmp=1;
    cout<<"XOR\n";
}
if(cmp==0)cout<<"IMPOSSIBLE";
```
cmp紀錄有沒有符合的
### 完整程式碼
```cpp=1
#include<bits/stdc++.h>
using namespace std;
int a,b;
bool c;
int main(){
    cin>>a>>b>>c;
    a=(a!=0?1:0),b=(b!=0?1:0);
    bool cmp=0;
    if((a&&b)==c){
        cmp=1;
        cout<<"AND\n";
    }
    if((a||b)==c){
        cmp=1;
        cout<<"OR\n";
    }
    if((a^b)==c){
        cmp=1;
        cout<<"XOR\n";
    }
    if(cmp==0)cout<<"IMPOSSIBLE";
}
```
## 第二題 交錯字串
### 題目敘述 
https://zerojudge.tw/ShowProblem?problemid=c462
### 解套
N/A
### 題目分析
首先因為題目關心的是「連續大(小)寫的交錯」，所以我們只需要知道連續大(小)寫區間的長度就好
一開始先$O(n)$掃過字串，把連續的大(小)寫子字串長度存進陣列segments

接著就是遍歷segments以獲得答案(以ans紀錄)
我的作法是開一個變數$tmp$紀錄第一個遇到長度為k的子字串的位置，
然後持續遍歷直到遇到長度不是$k$的子字串
假設該位置是$i$，代表總共有$i-tmp$個子字串，不過這時還要考慮前一個$(tmp-1)$以及後一個$(i)$子字串長度是否大於k，因為這樣也能對答案有貢獻
(例：$k=1$,$s=aa B a BB$，答案會是$aBaB$而非$Ba$)
再和ans取較大值即可
需要注意的是每處理完一段就要把$tmp$變回-1，避免重複計算
最後輸出$ans×k$，因為ans紀錄的是子字串數量

時間複雜度$O(n)$
### 解題流程
輸入$→$生成segments$→$計算答案$→$輸出
### 解題過程
#### 變數宣告
```cpp=3
int k,ans=0;
string s;
vector<int>segments;
```
變數名同題述、前述
#### 輸入優化
```cpp=7
ios::sync_with_stdio(0);
cin.tie(0);
```
不加也會過
#### 輸入
```cpp=9
cin>>k>>s;
```
#### 產生segments
```cpp=10
int tmp=1;
for(int i=1;i<s.size();i++){
    if(isupper(s[i])==isupper(s[i-1]))tmp++;
    else{
        segments.push_back(tmp);
        tmp=1;
    }
}
if(tmp)segments.push_back(tmp);
```
tmp存當前子字串長度
#### 更新答案
```cpp=19
tmp=-1;
for(int i=0;i<segments.size();i++){
    if(segments[i]==k){
        if(tmp==-1)tmp=i;
        else continue;
    }
    else{
        if(tmp==-1)continue;
        else{
            int num=i-tmp;
            if(segments[i]>k)num++;
            if(tmp!=0&&segments[tmp-1]>k)num++;
            ans=max(ans,num);
            tmp=-1;
        }
    }
}
if(tmp!=-1){
    int num=segments.size()-tmp;
    if(tmp!=0&&segments[tmp-1]>k)num++;
    ans=max(ans,num);
}
```
處理方法同前述，只是最後要再確認一遍(這邊因為沒有「後一個」所以不用判斷)
#### 輸出
```cpp=41
cout<<ans*k;
```
### 完整程式碼
```cpp=1
#include<bits/stdc++.h>
using namespace std;
int k,ans=0;
string s;
vector<int>segments;
int main(){
    ios::sync_with_stdio(0);
    cin.tie(0);
    cin>>k>>s;
    int tmp=1;
    for(int i=1;i<s.size();i++){
        if(isupper(s[i])==isupper(s[i-1]))tmp++;
        else{
            segments.push_back(tmp);
            tmp=1;
        }
    }
    if(tmp)segments.push_back(tmp);
    tmp=-1;
    for(int i=0;i<segments.size();i++){
        if(segments[i]==k){
            if(tmp==-1)tmp=i;
            else continue;
        }
        else{
            if(tmp==-1)continue;
            else{
                int num=i-tmp;
                if(segments[i]>k)num++;
                if(tmp!=0&&segments[tmp-1]>k)num++;
                ans=max(ans,num);
                tmp=-1;
            }
        }
    }
    if(tmp!=-1){
        int num=segments.size()-tmp;
        if(tmp!=0&&segments[tmp-1]>k)num++;
        ans=max(ans,num);
    }
    cout<<ans*k;
}
```
## 第三題 樹狀圖分析
### 題目敘述
https://zerojudge.tw/ShowProblem?problemid=c463
### 解套
dfs
### 題目分析
這明顯是一題dfs/bfs的題目，只是題目沒有給根節點，需要自己找，然後他求的答案有點神奇，是「某節點和最遠子節點的距離」的總和

(我是用dfs，然後存圖是用鄰接串列)

根節點的部分我是用unordered_set來求，一開始先把從1到n的數字都放進去，在存圖時遇到是別人子節點的就把他從unordered_set裡拿掉，最後剩下來的就是根節點

dfs部分在函式裡我用了兩個參數id和dep，紀錄處理的節點編號與深度，另外再開一個far陣列紀錄每個節點「最遠子節點的深度」，而實際在dfs時分成兩種狀況：
1. 葉節點(用子節點陣列大小是0判斷)：這時為了方便就把葉節點視為自己，所以他的far值就是深度
2. 非葉節點：往下遍歷，並在遍歷過程中不斷更新far的值(自己和葉節點的far值取max)

最後把答案加上「far值檢調自己的深度」，全部跑完後就會是答案
喔對然後答案會爆int$(10^{10}>2^{31})$

時間複雜度$O(n)$
### 解題流程
輸入、存圖$→$dfs$→$輸出
### 解題過程
#### 變數宣告
```cpp=3
int n,a,b;
long long ans=0;
vector<vector<int>>v;
unordered_set<int>st;
vector<int>far;
```
n同題述，a,b只是輸入用，其他同前述
#### dfs函式
```cpp=8
void dfs(int id,int dep){
    if(v[id].size()==0){
        far[id]=dep;
        return;
    }
    for(int i:v[id]){
        dfs(i,dep+1);
        far[id]=max(far[id],far[i]);
    }
    ans+=far[id]-dep;
    return;
}
```
原理同前述
#### 輸入優化
```cpp=21
ios::sync_with_stdio(0);
cin.tie(0);
```
不加也行
#### 輸入、初始化、存圖
```cpp=23
cin>>n;
v.assign(n+1,{});
far.assign(n+1,0);
for(int i=1;i<=n;i++)st.insert(i);
for(int i=1;i<=n;i++){
    cin>>a;
    for(a;a>0;a--){
        cin>>b;
        v[i].push_back(b);
        st.erase(b);
    }
}
```
輸入n後決定v,far的大小，並初始化st
接著照順序存子節點，並更新st
#### dfs
```cpp=35
dfs(*st.begin(),0);
```
從根節點開始遍歷，深度設為0
#### 輸出
```cpp=36
cout<<*st.begin()<<"\n"<<ans;
```
### 完整程式碼
```cpp=1
#include<bits/stdc++.h>
using namespace std;
int n,a,b;
long long ans=0;
vector<vector<int>>v;
unordered_set<int>st;
vector<int>far;
void dfs(int id,int dep){
    if(v[id].size()==0){
        far[id]=dep;
        return;
    }
    for(int i:v[id]){
        dfs(i,dep+1);
        far[id]=max(far[id],far[i]);
    }
    ans+=far[id]-dep;
    return;
}
int main(){
    ios::sync_with_stdio(0);
    cin.tie(0);
    cin>>n;
    v.assign(n+1,{});
    far.assign(n+1,0);
    for(int i=1;i<=n;i++)st.insert(i);
    for(int i=1;i<=n;i++){
        cin>>a;
        for(a;a>0;a--){
            cin>>b;
            v[i].push_back(b);
            st.erase(b);
        }
    }
    dfs(*st.begin(),0);
    cout<<*st.begin()<<"\n"<<ans;
}
```
## 第四題 物品堆疊
### 題目敘述
https://zerojudge.tw/ShowProblem?problemid=c471
### 解套
依照貪心法則排序
### 題目分析
這是一題貪心題，~~但如果看不出來好像也沒辦法？~~
我想到比較有邏輯的方式(不是通靈/靠經驗)是用複雜度分析；因為題目的$n$最大到$10^5$，時限1秒，
所以$O(n^2)$會超時，大概只能$O(nlogn)$或$O(n)$，但要$O(n)$著實有些困難(至少我想不到)，
而如果要$O(nlogn)$第一個想到的大概就是排序吧？

不過這題不能單純要內建排序，需要自己寫自定義排序的比較規則。
因為自定義排序韓式只需要寫兩個東西的比較，所以我們只需要思考針對兩個物品怎麼排序就好

假設有A,B兩個物品，若A放上面，那要拿B時就會花w(A)×f(B)的能量，同理如果B放上面，那要拿A時就會花w(B)×f(A)的能量，而題目希望最小化消耗的能量，所以讓比較小的排上面(要拿較多次)

排序完後從頭遍歷一邊累計搬運次數，一邊將答案加上重量乘次數，就能得到最後答案
另外這一題會爆int(如果有100000個物品每個都重1000要拿1000次...)，所以要開long long

時間複雜度$O(nlogn)$
### 解題流程
輸入$→$排序$→$計算$→$輸出
### 解題過程
#### define
```cpp=2
#define int long long
#define w first
#define f second
```
方便起見，另外pair前面存重量、後面存次數
#### 變數宣告
```cpp=6
int n,ans=0;
pair<int,int>arr[100000+10];
```
n同題述，ans存答案，arr存物品
#### 自定義排序
```cpp=8
bool cmp(pair<int,int> a,pair<int,int> b){
    return a.w*b.f<b.w*a.f;
}
```
邏輯同前述
#### 輸入優化
```cpp=12
ios::sync_with_stdio(0);
cin.tie(0);
```
不加也會過
#### 輸入
```cpp=14
cin>>n;
for(int i=0;i<n;i++)cin>>arr[i].w;
for(int i=0;i<n;i++)cin>>arr[i].f;
```
#### 排序
```cpp=17
sort(arr,arr+n,cmp);
```
cmp要記得打
#### 計算答案
```cpp=18
int cnt=arr[0].w;
for(int i=1;i<n;i++){
    ans+=arr[i].f*cnt;
    cnt+=arr[i].w;
}
```
cnt紀錄累積的重量，因為最上面的不需要消耗能量，所以cnt預設為第0個物品的重量，而計算答案從第1個物品開始
#### 輸出
```cpp=23
cout<<ans;
```
### 完整程式碼
```cpp=1
#include<bits/stdc++.h>
#define int long long
#define w first
#define f second
using namespace std;
int n,ans=0;
pair<int,int>arr[100000+10];
bool cmp(pair<int,int> a,pair<int,int> b){
    return a.w*b.f<b.w*a.f;
}
signed main(){
    ios::sync_with_stdio(0);
    cin.tie(0);
    cin>>n;
    for(int i=0;i<n;i++)cin>>arr[i].w;
    for(int i=0;i<n;i++)cin>>arr[i].f;
    sort(arr,arr+n,cmp);
    int cnt=arr[0].w;
    for(int i=1;i<n;i++){
        ans+=arr[i].f*cnt;
        cnt+=arr[i].w;
    }
    cout<<ans;
}
```