---
# System prepended metadata

title: 2021 第一屆 NHDK Ten Point Round 初學者程式設計交流會 - 題解

---

# 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<<" ";
}
```
:::
