# 2021 第一屆 NHDK Ten Point Round 初學者程式設計交流會 - 題解
## 團體賽
### Problem A 同心協力
**Prepared by Colten**
簽到題,給大家祝福的題目ww
考點 : 輸出
:::spoiler 參考解答(C++)
```cpp=
#include <bits/stdc++.h>
#define int long long
using namespace std;
signed main(void)
{
ios_base::sync_with_stdio(false);
cin.tie(0);
cout << "Team Fighting!\n";
return 0;
}
```
:::
### Problem B 幸運的一天
**Prepared by Colten**
考點 : 字串
利用 **字串** 去比對每一個索引的字元是否為質數,然後最後確定數量是否剛好等於 $2$
:::spoiler 參考解答(C++)
```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;
cin >> a;
int check = 0;
for(int i=0;i<a.size();i++)
{
if( a[i] == '2' || a[i] == '3' || a[i] == '5' || a[i] == '7' )
{
check++;
}
}
cout << ( check == 2 ? "Lucky Day!\n" : "Normal Day\n");
return 0;
}
```
:::
### Problem C 數字多樣性
**Prepared by Colten**
考點 : 資料結構(STL-set)
利用 set 元素獨一無二(不會有重複的集合)的特性,我們可以將所有的數字都拉近一個集合裡面
最後在將 set 的 size 大小輸出,這就是最後數字種類的數目
:::spoiler 參考解答(C++)
```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 n,m,i,k;
cin >> n >> m;
set <int> s;
for(i=0;i<n;i++)
{
for(k=0;k<m;k++)
{
int input;
cin >> input;
s.insert(input);
}
}
cout << s.size() << "\n";
return 0;
}
```
:::
### Problem D 公平的數列
**Prepared by Colten**
考點 : 實作
一開始我們可以先取得數列第二項以及第一項之差做為公差值
接著在輸入的同時,檢查是否有任相鄰兩項的差不是公差值
:::spoiler 參考解答(C++)
```cpp=
#include <bits/stdc++.h>
#define int long long
using namespace std;
int a[200005];
signed main(void)
{
ios_base::sync_with_stdio(false);
cin.tie(0);
int q,n,i,k;
cin >> q;
while(q--)
{
bool ans = true;
cin >> n;
int d;
for(i=0;i<n;i++)
{
cin >> a[i];
if( i == 1 )
{
d = a[i] - a[i-1];
}
else if( i != 0 )
{
if( a[i] - a[i-1] != d ) ans = false;
}
}
if( ans == true ) cout << "Yes\n";
else cout << "No\n";
}
return 0;
}
```
:::
### Problem E 撈金魚
**Prepared by Colten**
考點 : 排序 & 貪婪
我們可以將金魚的重量以及網子承受重量先進行排序,最後兩邊都從最重的網子以及最重的金魚開始比較
當網子的承受重量不比金魚低時,就將答案加上金魚的重量
:::spoiler 參考解答(C++)
```cpp=
#include <bits/stdc++.h>
#define int long long
using namespace std;
int a[200005],k[200005];
signed main(void)
{
ios_base::sync_with_stdio(false);
cin.tie(0);
int q,n,i;
cin >> q;
for(i=0;i<q;i++)
{
cin >> a[i];
}
cin >> n;
for(i=0;i<n;i++)
{
cin >> k[i];
}
sort(a,a+q);
sort(k,k+n);
int ans = 0,index = n - 1;
for(i=q-1;i>=0;i--)
{
if( index < 0 ) break;
if( k[index] >= a[i] )
{
ans += a[i];
index--;
}
}
cout << ans << "\n";
return 0;
}
```
:::
### Problem F 滑雪
**Prepared by Colten**
考點 : 排序 & 動態規劃
這題如果使用 DFS(深度優先搜尋) 來做的話應該可以拿到 29 分
不過仔細想想題目給的條件,我們會發現是一個有向無環圖(DAG)
由於只能從高的山到達低的山,因此我們先對高度做個排序,將高度由大排到小
我們將 $dp[i]$ 定義為到達第 $i$ 座山的最遠距離
接著,我們從最高的山開始,我們對被選定的山 $u$ 去找比被選定的山還要高且能到達 $u$ 的山中,最大的 $dp[k]$
最後,$dp[u] = max( dp[k] ) + 1$
:::spoiler 參考解答(C++)
```cpp=
#include <bits/stdc++.h>
#define int long long
using namespace std;
vector <int> mp[200005];
int n,m,w[200005];
vector <int> dp(200005,-1e9);
signed main(void)
{
ios_base::sync_with_stdio(false);
cin.tie(0);
cin >> n >> m;
vector <pair<int,int>> w(n);
for(int i=0;i<n;i++)
{
cin >> w[i].first;
w[i].second = i;
}
for(int i=0;i<m;i++)
{
int a,b;
cin >> a >> b;
a--;
b--;
if( w[a] > w[b] )
{
mp[b].push_back(a);
}
else if( w[b] > w[a] )
{
mp[a].push_back(b);
}
}
sort(w.begin(),w.end(),greater<pair<int,int>>());
dp[0] = 0;
for( pair<int,int> i : w )
{
int u = i.second;
int cur = -1e9;
if( u == 0 ) continue;
for( int k : mp[u] )
{
cur = max(cur,dp[k]);
}
if( cur != -1e9 )
{
dp[u] = cur + 1;
}
}
if( dp[n-1] == -1e9 ) cout << -1 << "\n";
else cout << dp[n-1] << "\n";
return 0;
}
```
:::
### Problem G 對決
**Prepared by Fysty**
為了方便我們稱雷姆是先手,翔子是後手。
首先考慮沒有$k$的情況,透過觀察會發現後手只會在$n$是$m+1$的倍數時才會贏,用遞迴關係和數學歸納法可以輕鬆得證,就不多說。(必勝策略大概就是一個人移動$x$格,另一個人就移動$m+1-x$格)
加上$x-k$這個操作感覺會差很多,但實際上不會,如果$k$不是$m+1$的倍數,那麼其實跟以上的必勝策略相同,因為目標都是每兩個回合都總共移動$m+1$的倍數個格子,所以答案跟沒有$k$一樣。
如果$k$是$m+1$的倍數,那麼當$n<k$時結論也一樣,但$n=k$時,先手會贏,
隨之而來的是$n=k+1$時,後手會贏,不過這時透過觀察發現$n=k+2,k+3,...,2k+2$的結論亦同,這時再使用數學歸納法可以得知,$n=t(k+1)+i$的結論跟$n=i$的結論相同,所以需要做的就些簡單整除關係判斷就好了。
:::spoiler 參考解答(C++)
```cpp=
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define MottoHayaku ios::sync_with_stdio(false);cin.tie(0);
int main()
{
MottoHayaku
ll t;
cin>>t;
while(t--)
{
ll n,m,k;
cin>>n>>m>>k;
if(k<=m||k%(m+1))
{
if(n%(m+1)==0) cout<<"Shoko\n";
else cout<<"Rem\n";
}
else
{
n%=(k+1);
if(n==k||n%(m+1)) cout<<"Rem\n";
else cout<<"Shoko\n";
}
}
}
```
:::
## 個人賽
### Problem A Hello 2021 Ten Point Round!
**Prepared by Colten**
考點 : 輸出
送上祝福的題目ww
:::spoiler 參考解答(C++)
```cpp=
#include <bits/stdc++.h>
#define int long long
using namespace std;
signed main(void)
{
ios_base::sync_with_stdio(false);
cin.tie(0);
cout << "Hello 2021 Ten Point Round in Tainan!\n";
return 0;
}
```
:::
### Problem B 獎勵查詢
**Prepared by Colten**
考點 : 條件判斷
照著題目列舉每一種狀況或紀錄輸入的分數可以得到什麼獎勵皆可
:::spoiler 參考解答(C++)
```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 n;
cin >> n;
bool a,b,c,d;
a = b = c = d = 0;
if( n >= 60 ) a = 1;
if( n >= 70 ) b = 1;
if( n >= 80 ) c = 1;
if( n >= 90 ) d = 1;
cout << "Drink ";
if( d == 1 ) cout << "Yes\n";
else cout << "No\n";
cout << "Cookie ";
if( c == 1 ) cout << "Yes\n";
else cout << "No\n";
cout << "Stationery ";
if( b == 1 ) cout << "Yes\n";
else cout << "No\n";
cout << "Water ";
if( a == 1 ) cout << "Yes\n";
else cout << "No\n";
return 0;
}
```
:::
### Problem C 連續加分
**Prepared by Colten**
考點 : 實作
在輸入的時候一併去紀錄目前連續到多少,並對目前記錄的數跟目前最大的連續數去做比較,取最大值並更新
:::spoiler 參考解答(C++)
```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 n,i,k,ans = 0,now = 0;
cin >> n;
for(i=0;i<n;i++)
{
int input;
cin >> input;
if( input > 0 )
{
now++;
ans = max(ans,now);
}
else
{
now = 0;
}
}
cout << ans << "\n";
return 0;
}
```
:::
### Problem D 琳瑯滿目
**Prepared by Colten**
考點 : 二分搜尋
將書籍的編號排序後,對每次查詢的數字取 upper_bound 以及 lower_bound,並將兩數相減即為所求
:::spoiler 參考解答(C++)
```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 n,i,k;
vector <int> v;
cin >> n;
for(i=0;i<n;i++)
{
int input;
cin >> input;
v.emplace_back(input);
}
sort(v.begin(),v.end());
int q;
cin >> q;
while(q--)
{
int input;
cin >> input;
auto i1 = upper_bound(v.begin(),v.end(),input);
auto i2 = lower_bound(v.begin(),v.end(),input);
cout << i1 - i2 << "\n";
}
return 0;
}
```
:::
### Problem E 風原特色圓環 Traffic Circle III
**Prepared by Colten**
**改編自交大 2020 CPTC 程式競賽 pB**
考點 : 排序 & 貪婪
一開始我們先紀錄每一個圓環連接了幾條道路
然後我們可以知道,如果我們想要使架設路燈的金額最便宜
我們會使 **連接最多條道路的圓環,架設最便宜的路燈**
:::spoiler 參考解答(C++)
```cpp=
#include <bits/stdc++.h>
#define int long long
using namespace std;
int light[100005],circle[100005];
signed main(void)
{
ios_base::sync_with_stdio(false);
cin.tie(0);
int q,n,i,k;
cin >> n;
for(i=0;i<n;i++)
{
cin >> light[i];
}
cin >> q;
while(q--)
{
int x,y;
cin >> x >> y;
circle[x-1]++;
circle[y-1]++;
}
sort(light,light+n);
sort(circle,circle+n);
int ans = 0;
for(i=0;i<n;i++)
{
ans += circle[n-i-1] * light[i];
}
cout << ans << "\n";
return 0;
}
```
:::
### Problem F MEX it!
**Prepared by Fysty**
把所有重複的整數刪掉,假設刪掉了$a$個。
假設最後的飽和的陣列中最大的整數是$M$,則$M$一定在最一開始的陣列中,否則我可以省略掉一次MEX使答案變更小,故存在$i$使得原本的陣列中(已經刪掉所有重複的元素),前$i$個都在最後的陣列中且其他通通都被刪掉,所以我們可以用滾動的方式跑過$i=0,1,...,n$,找出刪掉的最小數量$b$。
答案即為$a+b$。
:::spoiler 參考解答(C++)
```cpp=
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define MottoHayaku ios::sync_with_stdio(false);cin.tie(0);
set<ll> s;
int main()
{
MottoHayaku
ll n;
cin>>n;
for(int i=0;i<n;i++)
{
ll a;
cin>>a;
s.insert(a);
}
ll tmp=n-s.size();
ll ans=s.size(),cur=0;
while(!s.empty())
{
ll a=*s.begin();
s.erase(s.begin());
cur++;
ans=min(ans,a+1-cur+s.size());
}
cout<<ans+tmp;
}
```
:::
### Problem G 卡牌遊戲
**Prepared by Colten**
考點 : 資料結構(STL-priority_queue)
這題的操作其實是在暗中提示使用**優先佇列**來做
我們將抽排以及一開始擁有的牌都使用 push 加進優先佇列之中
由於優先佇列中,預設最上層的元素會是最大值,因此對於出牌以及丟牌這兩個操作
我們可以使用 pop 來完成,接著就是照著題目的規則實作的部分了
:::spoiler 參考解答(C++)
```cpp=
#include <bits/stdc++.h>
#define int long long
using namespace std;
priority_queue <int> a;
priority_queue <int> b;
void p1()
{
for(int i=0;i<3;i++)
{
int input;
cin >> input;
a.push(input);
}
}
void p2()
{
for(int i=0;i<3;i++)
{
int input;
cin >> input;
b.push(input);
}
}
signed main(void)
{
ios_base::sync_with_stdio(false);
cin.tie(0);
int t,n;
cin >> t >> n;
for(int i=0;i<t;i++)
{
int input;
cin >> input;
a.push(input);
}
for(int i=0;i<t;i++)
{
int input;
cin >> input;
b.push(input);
}
int ans1 = 0,ans2 = 0;
while(n--)
{
int x = a.top();
int y = b.top();
a.pop();
b.pop();
if( x > y )
{
ans1 += 5;
ans2 += 3;
p1();
p2();
a.pop();
a.pop();
b.pop();
}
else if( y > x )
{
ans1 += 3;
ans2 += 5;
p1();
p2();
a.pop();
b.pop();
b.pop();
}
else
{
ans1 += 3;
ans2 += 3;
p1();
p2();
}
}
cout << ans1 << " " << ans2 << "\n";
return 0;
}
```
:::
### Problem H 插隊
**Prepared by Colten**
考點 : 陣列
我們可以開兩個陣列去記錄目前編號 $i$ 的前面是誰,以及後面是誰
接著插隊進行的時候去對插隊者前面的人以及後面的人,還有被插隊者前面的人以及後面的人
以及插隊者跟被插隊者,對這幾個人紀錄的陣列進行更新
:::spoiler 參考解答(C++)
```cpp=
#include <bits/stdc++.h>
#define int long long
using namespace std;
int f[200005],b[200005];
signed main(void)
{
ios_base::sync_with_stdio(false);
cin.tie(0);
int n,i,k;
cin >> n;
for(i=1;i<=n;i++)
{
f[i] = i - 1;
b[i] = i + 1;
}
int q;
cin >> q;
while(q--)
{
int x,y;
cin >> y >> x;
f[b[x]] = f[x];
b[f[x]] = b[x];
f[x] = f[y];
b[f[y]] = x;
f[y] = x;
b[x] = y;
}
int start;
for(i=1;i<=n;i++)
{
if( f[i] == 0 )
{
start = i;
break;
}
}
int now = 0,last = start;
while( now != n )
{
if( now == 0 )
{
cout << start << " ";
}
else
{
cout << b[last] << " ";
last = b[last];
}
now++;
}
return 0;
}
```
:::
### Problem I 密碼
**Prepared by Fysty**
令$A=AND(p),B=XOR(p),C=OR(p)$
我們分開討論$n$個數的第$i$位元有$cnt_i$種組合後,把$cnt_i$全部乘起來就好了。
如果$A$的第$i$位元是$1$或$C$的第$i$位元是$0$,那顯然前者只有全$1$,後者只有全$0$才能滿足條件,故$cnt_i=1$。
不然的話,先只考慮$B$的第$i$個位元$b$會發現若$b=0$則總要有偶數個$1$,若$b=1$則總要有奇數個$1$,此時都是$cnt_i=2^{n-1}$,然後考慮一些特殊情況:
若$b=0$且$C$的第$i$位元是$1$,則會發現全為$0$不滿足條件所以多算,故$cnt_i$要減一。
若$b=0,n$為偶數且$A$的第$i$位元是$0$,則會發現全為$1$不滿足條件所以多算,故$cnt_i$要減一。
若$b=1,n$為奇數且$A$的第$i$位元是$0$,則會發現全為$1$滿足前者但違反後者,故$cnt_i$要減一。
乖乖地判掉以上情況後,就能算出真正的$cnt_i$了。
:::spoiler 參考解答(C++)
```cpp=
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define MottoHayaku ios::sync_with_stdio(false);cin.tie(0);
const int MOD=1e9+7;
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);
}
int main()
{
MottoHayaku
ll t;
cin>>t;
while(t--)
{
ll n,k;
cin>>n>>k;
ll x=0,y=(1LL<<k)-1,z=0;
for(int i=0;i<n;i++)
{
ll a;
cin>>a;
x^=a,y&=a,z|=a;
}
ll ans=1,m=fpow(2,n-1,MOD);
for(int i=0;i<k;i++)
{
ll a=y&1,b=z&1,c=x&1;
y>>=1,z>>=1,x>>=1;
if(a||!b) continue;
if(c)
{
if(n&1) ans=ans*(m-1)%MOD;
else ans=ans*m%MOD;
}
else
{
if(n&1) ans=ans*(m-1)%MOD;
else ans=ans*(m-2)%MOD;
}
}
cout<<ans<<"\n";
}
}
```
:::
### Problem J FST 的煩惱
**Prepared by Fysty**
題目就是大數的質因數分解,用常見的方式做是$O(\sqrt{n})$,這顯然會TLE,不過或許能撈到不少的分數。
整個題敘其實就是Pollard's rho的概念和原理,有興趣的人可以上網自己google。
這題比較需要注意的大概是各種實作細節,這裡就直接附上AC code給大家參考。
:::spoiler 參考解答(C++)
```cpp=
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define MottoHayaku ios::sync_with_stdio(false);cin.tie(0);
vector<ll> ans;
ll x[105];
ll mul(ll a,ll b,ll p)
{
a%=p,b%=p;
ll ret=0;
while(b)
{
if(b&1)
{
ret+=a;
ret%=p;
}
a=(a+a)%p;
b>>=1;
}
return ret;
}
ll fpow(ll a,ll b,ll p)
{
ll ans=1;
while(b)
{
if(b%2) ans=mul(ans,a,p);
a=mul(a,a,p);
b/=2;
}
return ans;
}
bool MR(ll n)
{
if(n%2==0)
{
if(n==2) return 1;
else return 0;
}
ll k=n-1,r=0,q=20;
while(k%2==0) r++,k/=2;
while(q--)
{
ll a=rand()%(n-2)+2;
x[0]=fpow(a,k,n);
for(int i=1;i<=r;i++)
{
x[i]=mul(x[i-1],x[i-1],n);
if(x[i]==1&&x[i-1]!=1&&x[i-1]!=n-1) return 0;
}
if(x[r]!=1) return 0;
}
return 1;
}
ll rho(ll n,ll c)
{
ll cur=1,k=2,x=rand()%(n-1)+1,y=x,p;
while(1)
{
x=(mul(x,x,n)+c)%n;
p=__gcd((x-y+n)%n,n);
if(p!=1&&p!=n) return p;
if(x==y) return n;
cur++;
if(cur==k) y=x,k*=2;
}
}
void findans(ll n,ll c)
{
if(n==1) return;
if(MR(n))
{
ans.push_back(n);
return;
}
ll p=n,k=c%n;
while(p>=n) p=rho(p,c--);
findans(p,k);
findans(n/p,k);
}
int main()
{
MottoHayaku
ll n;
cin>>n;
while(n%2==0)
{
ans.push_back(2);
n/=2;
}
findans(n,107);
cout<<ans.size()<<"\n";
sort(ans.begin(),ans.end());
for(ll u:ans) cout<<u<<" ";
}
```
:::