# 2020 年 10 月南區四校聯合初學者程式設計線上練習賽 題解
Div 1 Problem J 的題解跟題敘測資等等暫時不能提供
:)
## Div.2 Problem A
### By Fysty
**考點:大數輸入**
通常遇到輸入或處理大數時,不能用int或long long存,要用string存
::: spoiler 參考作法
```cpp=
#include <bits/stdc++.h>
using namespace std;
#define MotoHaiyaku ios::sync_with_stdio(false);cin.tie(0);
int main()
{
MotoHaiyaku
string s;
cin>>s;
cout<<s;
}
```
:::
## Div.2 Problem B 風原折價券問題
### By Colten
**考點:迴圈**
對於每種折價卷的配對最先注意的是補貼的金額須為最少
第二個是要從這些組合裡選出 500 元使用最多的方案
在賽中看到很多人都只拿了 30 分,很多人的錯誤都是
1. 500 元不是使用最多的一種方案
2. 題目敘述有說 500 元 或 200 元的使用最多 5 張
::: spoiler 參考作法
```cpp=
#include <iostream>
#include <cmath>
#include <algorithm>
#define int long long
using namespace std;
signed main(void)
{
int n,i,k;
int ans = 1e9;
int a,b,c;
cin >> n;
for(i=0;i<=5;i++)
{
for(k=0;k<=5;k++)
{
if(n-(i*500+k*200) >= 0)
{
if(ans >= n-(i*500+k*200))
{
ans = n-(i*500+k*200);
a = i;
b = k;
c = ans;
}
}
}
}
cout << a << " " << b << " " << c << "\n";
return 0;
}
```
:::
## Div.2 Problem C
### By Fysty
首先從$AND$和$XOR$的定義可以發現:對於任何的兩個數$x,y$,$a=x\&y$和$b=x\oplus y$的二進位表示中同一個位數不會都是$1$。
所以若輸入的$a,b$滿足$a\&b\neq0$則沒有答案,輸出$-1$。
否則注意到對於任何的兩個數$x,y$,$x+y=2(x\&y)+(x\oplus y)$
所以答案即為$2a+b$。
::: spoiler 參考作法
```cpp=
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define MotoHaiyaku ios::sync_with_stdio(false);cin.tie(0);
int main()
{
MotoHaiyaku
ll t;
cin>>t;
while(t--)
{
ll a,b;
cin>>a>>b;
if(a&b) cout<<"-1\n";
else cout<<2*a+b<<"\n";
}
}
```
:::
## Div.2 Problem D
### By Fysty
我們定義相鄰為之間不存在其他還活著的大兔。
經過觀察可以發現若某隻大兔$A$的飢餓度小於所有大兔中最大飢餓度$M$,則$A$一定在某步中被吃掉。
所以到最後只會剩下飢餓度為$M$的所有大兔。
換句話說,這題的答案就是$M$出現的所有位置。
::: spoiler 參考作法
```cpp=
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll INF=2*1e18;
#define MotoHaiyaku ios::sync_with_stdio(false);cin.tie(0);
ll a[200005];
int main()
{
MotoHaiyaku
ll t;
cin>>t;
while(t--)
{
ll n,mx=-INF;
cin>>n;
for(int i=1;i<=n;i++)
{
cin>>a[i];
mx=max(mx,a[i]);
}
for(int i=1;i<=n;i++)
{
if(a[i]==mx) cout<<i<<" ";
}
cout<<"\n";
}
}
```
:::
## Div.2 Problem E 電子日曆 1
### By joylintp
對於字串中的每個字元,如果該字元為數字,就將答案加上點亮該數字需要的燈管數;否則就忽略該字元。
可以將每個數字需要的燈管數存在陣列中以加快實作。
::: spoiler 參考作法
```cpp=
#include<bits/stdc++.h>
using namespace std;
signed main()
{
ios_base::sync_with_stdio(false);
cin.tie(0);
int arr[] = {6, 2, 5, 5, 4, 5, 6, 3, 7, 6};
string s, t;
cin >> s >> t;
int ans = 0;
for (char c : s)
if (isdigit(c))
ans += arr[c - '0'];
for (char c : t)
if (isdigit(c))
ans += arr[c - '0'];
cout << ans << '\n';
return 0;
}
```
:::
## Div.2 Problem F / Div. 1 Problem A 綠幹線公車的完美比例
### By Colten
**考點: 迴圈 & 實作**
對於每個查詢的 OuO 數,我們可以先利用雙重迴圈去搜每個區段間的值
加入 set 裡 當在搜尋時即利用 count 去做查詢
當 count 的值為不等於 $0$ 時,輸出 $Yes$ ,反之則輸出 $No$
::: spoiler 參考作法
```cpp=
#include <bits/stdc++.h>
#define int long long
using namespace std;
signed main(void)
{
ios_base::sync_with_stdio(false);
cin.tie(0);
int q,n,i,k;
cin >> q;
while(q--)
{
cin >> n;
set <int> a;
vector <int> v;
while(n--)
{
int input;
cin >> input;
v.push_back(input);
}
for(i=0;i<v.size();i++)
{
for(k=i+1;k<v.size();k++)
{
a.insert(v[i]+v[k]);
}
}
int t;
cin >> t;
while(t--)
{
int ouo;
cin >> ouo;
if(a.count(ouo) != 0)
{
cout << "Yes\n";
}
else
{
cout << "No\n";
}
}
a.clear();
v.clear();
}
return 0;
}
```
:::
## Div.2 Problem G / Div. 1 Problem B 電子日曆 2
### By joylintp
可以發現需要最少燈管的時間為 `2111/11/11 11:11:11`,需要 31 根燈管;最多燈管的時間為 `2888/08/08 08:08:08`,需要 91 根燈管。故當 $n < 31$ 或 $n > 91$ 時無解,輸出 `-1` 即可,可得 5 分。
枚舉 2020 年中的每一秒,計算該秒所需的燈管數,可得 35 分。
可以發現,年分的後三位數和小時、分鐘、秒鐘的個位數不管其他位置的情況為何皆可設定成 0 ~ 9 之間的任何數字。故當 $n \le 61$ 時,可以從 `2111/11/11 11:11:11` 開始,每多一個燈管就將上述的其中一位數改成多使用一個燈管的數字;而若 $n \ge 61$ 時,可以從 `2111/08/08 01:01:01` 開始,每多一個燈管就將上述其中一位數改成多使用一個燈管的數字。
::: spoiler 參考作法
```cpp=
#include<bits/stdc++.h>
using namespace std;
signed main()
{
ios_base::sync_with_stdio(false);
cin.tie(0);
int n;
cin >> n;
if (n < 31 || n > 91)
cout << "-1\n";
else
{
int t, idx[] = {1, 2, 3, 12, 15, 18};
string s, arr = "174208";
if (n > 61)
t = n - 61, s = "2111/08/08\n01:01:01";
else
t = n - 31, s = "2111/11/11\n11:11:11";
for (int i = 0; i < 6; i++)
s[idx[i]] = arr[min(t, 5)], t -= min(t, 5);
cout << s << '\n';
}
return 0;
}
```
:::
## Div.2 Problem H / Div. 1 Problem C 字母加法問題
### By Colten
**考點: 字元 & 實作**
此題提供其中一種思考路線
1. 判斷字元是否為0~9的數字,如果是可以直接將數字接上,否則須將大寫字母轉成數字
2. 如果接數字時該數字 $>=10$ 則必須將數字做分解再將其接上
3. 最後將兩組接玩的數字相加
特別注意 : 賽中發現有人忘記開 long long 導致 WA 請記得在做此題目時要開 long long
::: spoiler 參考作法
```cpp=
#include <bits/stdc++.h>
#define int long long
using namespace std;
signed main(void)
{
ios_base::sync_with_stdio(false);
cin.tie(0);
string a,b;
int i,n,k;
cin >> a >> b;
int x = 0,y = 0;
for(i=0;i<a.size();i++)
{
if(a[i]>='A' && a[i] <= 'Z')
{
if(a[i]>='J')
{
int u = (a[i]-'A'+1);
vector <int> c;
while(u!=0)
{
c.push_back(u%10);
u /= 10;
}
for(k=c.size()-1;k>=0;k--)
{
x *= 10;
x += c[k];
}
}
else
{
x *= 10;
x += (a[i]-'A'+1);
}
}
else
{
x *= 10;
x += (a[i]-'0');
}
}
for(i=0;i<b.size();i++)
{
if(b[i]>='A' && b[i] <= 'Z')
{
if(b[i]>='J')
{
int u = (b[i]-'A'+1);
vector <int> c;
while(u!=0)
{
c.push_back(u%10);
u /= 10;
}
for(k=c.size()-1;k>=0;k--)
{
y *= 10;
y += c[k];
}
}
else
{
y *= 10;
y += (b[i]-'A'+1);
}
}
else
{
y *= 10;
y += (b[i]-'0');
}
}
cout << x + y << "\n";
return 0;
}
```
:::
## Div.2 Problem I / Div. 1 Problem D Colten 與 貢丸 的優先客人系統
### By Colten
**考點: STL-Set**
可以將非 $-1,-2$ 的數字加進 set 集合內 對於每筆查詢
可以利用 begin,rbegin,*max_element,*min_element 等等...
去取得最大值以及最小值
特別注意 :
1. set 必須開 multiset
2. 如果是直接把最大值或最小值 erase 的人記得要先查好剩下的還有幾個 erase 完後要 insert 回去
::: spoiler 參考作法 (element)
```cpp=
#include <iostream>
#include <set>
#include <algorithm>
using namespace std;
int main(void)
{
cin.tie(0);
ios_base::sync_with_stdio(false);
multiset <long long int> myset;
long long int q,n,i,k;
cin >> q;
while(q--)
{
while(cin>>n)
{
if(n==0)
{
cout << "\n";
myset.clear();
break;
}
else if(n<0)
{
if(myset.size() == 0)
{
cout << -1 << " ";
continue;
}
if(n==-2)
{
long long int count = 0,big= *max_element(myset.begin(),myset.end());
count = myset.count(big) - 1;
cout << big << " ";
myset.erase(big);
for(i=0;i!=count;i++)
{
myset.insert(big);
}
}
else if(n==-1)
{
long long int count = 0,small= *min_element(myset.begin(),myset.end());
count = myset.count(small) - 1;
cout << small << " ";
myset.erase(small);
for(i=0;i!=count;i++)
{
myset.insert(small);
}
}
}
else if(n>0)
{
myset.insert(n);
}
}
}
return 0;
}
```
:::
## Div.2 Problem J / Div. 1 Problem E 西北望鄉何處是 東南見月幾回圓
### By Colten
**考點: 數論 & 實作**
如果暴力解的話可能只會拿到部分的分數,這時我們就要來想想如何優化
對於 $(S_i=b_i*a_1*b_i*a_2*...*b_i*a_n)$ 我們可以將其化簡成
$S_i=(a_1+a_2+...+a_n)*b_i^n$
對於取得 $b_i^n$ 的值時我們可以使用 **快速冪** 來提升效率
這麼一來程式的執行效率就提升了很多 !
::: spoiler 參考作法
```cpp=
#include <bits/stdc++.h>
#define ll long long
using namespace std;
ll fpow(ll a,ll b,ll m)
{
if(!b) return 1;
ll ans=fpow(a*a%m,b/2,m);
return (b%2?ans*a%m:ans);
}
#define MotoHaiyaku ios::sync_with_stdio(false);cin.tie(0);
#define rep(i,n) for(ll i=0;i<n;i++)
signed main()
{
MotoHaiyaku
ll n,m,ans1=1,ans2=1,MOD=1e8+7;
cin>>n>>m;
rep(i,n+m)
{
ll a;
cin>>a;
if(i<n) ans1=ans1*a%MOD;
else ans2=ans2*a%MOD;
}
cout<<fpow(ans1,m,MOD)*fpow(ans2,n,MOD)%MOD;
}
```
:::
## Div. 1 Problem F 風原資訊社分組問題
### By Colten
**考點: 數論**
在進行分組的時候要讓 $GCD>1$ 最簡單的想法應該是讓每個分組的數
相加都為偶數,這麼一來就可以確保 $GCD >= 2$
所以我們只要確保以下幾點
1. 奇數 + 奇數 = 偶數
2. 偶數 + 偶數 = 偶數
開兩個陣列先把雞樹根偶數分類出來最後在依照以上兩點去做分組
如此一來即可達成題目對分組的要求
::: spoiler 參考作法
```cpp=
#include <bits/stdc++.h>
#define int long long
using namespace std;
signed main(void)
{
int n,i,k;
cin >> n;
int need = n - 1;
vector <int> odd;
vector <int> even;
for(i=0;i<2*n;i++)
{
int input;
cin >> input;
if( input % 2 == 1 )
{
odd.push_back(i+1);
}
else
{
even.push_back(i+1);
}
}
for(i=1;i<odd.size()&&need!=0;i+=2,need--)
{
cout << odd[i] << " " << odd[i-1] << "\n";
}
for(i=1;i<even.size()&&need!=0;i+=2,need--)
{
cout << even[i] << " " << even[i-1] << "\n";
}
return 0;
}
```
:::
## Div. 1 Problem G 風原飛鏢之中強大的 Qum 值
### By Colten
**考點: 思維 & 實作**
題目要求每個數之間的差不能小於 $2$
這題其實算一個梗題,提供兩種做法
1. 使用 deque 分別依序重複做 push_back 以及 push_front
2. 先輸出基數再輸出偶數 ex.(1 3 5 7 9 2 4 6 8 10)
以上兩點都可以達成題目的需求
特別注意 : 使用第一個作法時須先將 5 個數字排好再進行 push 的動作
這邊提供其中一種排法 (3 1 4 2 5)
::: spoiler 參考作法
```cpp=
#include <bits/stdc++.h>
#define int long long
using namespace std;
signed main(void)
{
ios_base::sync_with_stdio(false);
cin.tie(0);
int q,n,i,k;
cin >> q;
while(q--)
{
deque <int> a;
cin >> n;
a.push_back(3);
a.push_back(1);
a.push_back(4);
a.push_back(2);
a.push_back(5);
for(i=6;i<=n;i++)
{
if( i % 2 == 0 )
{
a.push_front(i);
}
else
{
a.push_back(i);
}
}
for(i=0;i<n;i++)
{
cout << a[i] << " ";
}
cout << "\n";
}
return 0;
}
```
:::
## Div. 1 Problem H
本題中二進位魔咒即為 de Bruijn sequence。
令子字串 $S(i, n)$ 轉換成整數的值為 $a_i$。對於所有 $i > 1$,$a_i$ 必為 $a_{i-1}\ mod\ 2^{n-1} \times 2$ 或 $a_{i-1}\ mod\ 2^{n-1} \times 2 + 1$,其中 $a\ mod\ b$ 表示 $a$ 除以 $b$ 的餘數。
暴力枚舉並檢查排列可得約 9 分。亦可人工枚舉並輸出 $n$ 值較小的結果。
其中一種快速的構造方式為,$a_1 = 0$,對於 $i > 1$,若 $a_{i-1}\ mod\ 2^{n-1} \times 2 + 1$ 尚未出現在序列 $a$ 中,則 $a_i = a_{i-1}\ mod\ 2^{n-1} \times 2 + 1$;否則,$a_i = a_{i-1}\ mod\ 2^{n-1} \times 2$。
在判斷一數是否已出現在序列中時,若使用 `set` 等資料結構會導致 $n$ 較大時 `Time Limit Exceeded`,可得到約 73 分。使用 `bool` 陣列即可得到滿分。
:::spoiler 參考作法
```cpp=
#include<bits/stdc++.h>
using namespace std;
bool use[1 << 25];
signed main()
{
ios_base::sync_with_stdio(false);
cin.tie(0);
int n;
cin >> n;
for (int i = 1; i < n; i++)
cout << 0;
int t = 1 << n, now = 0;
while (t--)
{
use[now] = true;
cout << (now & 1);
int tt = (now & ((1 << (n - 1)) - 1)) << 1;
if (!use[tt + 1])
now = tt + 1;
else
now = tt;
}
cout << '\n';
return 0;
}
```
:::
## Div. 1 Problem I
### By amano_hina
(working in progress)
這題很簡單吧~
先來講講要怎麼拿各個子題的分好了
子題1:題目範例
跳過
子題2:$y$是質數
注意到在$x$和$y$之間一定不存在一數被含有質數$y$的因子,根據一些簡單的數論知識,在這個子題中一定沒有滿足題意的解,故直接輸出$-1$即可通過這個子題
子題3:$y\le 10^{6}$
這個子題就直接把在$x$跟$y$之間的所有數看能不能整除,假設能整除就直接輸出此數和$xy$除以此數,假設跑到最後沒有任何數可整除$xy$,就輸出$-1$
最差時間複雜度為$O(yt)$
子題4:$y\le 10^{7}$
注意到
:::spoiler 參考程式碼
```cpp=
```
:::