# 2021 5 月 Ten Point Round #10 (Div.1 + Div.2) 題解
Hope you enjoyed the round!
<!---## Div.2--->
## pA 揮霍的土豪觀光客 (Splurge wealthy tourist)
**Prepared By Colten**
本題我們可以先計算如果繞完一整圈,會需要多少花費。
確定好一圈的總花費之後,我們就可以將此總花費除以 Jack 的預算。
不過假如預算無法被一圈的總花費整除,那麼代表勢必至少要再多跑一圈,才能確保將所有花費花光。
時間複雜度:$O(1)$
:::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 a,b,c,d,e;
cin >> a >> b >> c >> d >> e;
int input;
cin >> input;
cout << input / ( a + b + c + d + e ) + ( input % ( a + b + c + d + e ) != 0 ) << "\n";
return 0;
}
```
:::
## pB Gunjyo 與樹枝 (Gunjyo and branches)
**Prepared By Colten**
由於樹枝是可以被折斷的,因此我們可以很貪心的先將所有樹枝都先做一次排序,接著我們就可以每次都取目前可用的樹枝當中,樹枝長度第 $4$ 長的樹枝,並將第 $3,2,1$ 長的樹枝通通折斷成跟第 $4$ 長的樹枝的長度一樣,如此一來就可以很貪心的使所有的正方形都是大的。
時間複雜度:$O(N+NlogN)$
:::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;
vector <int> a(n);
for(int i=0;i<n;i++)
{
cin >> a[i];
}
sort(a.begin(),a.end());
int ans = 0;
for(int i=n-4;i>=0;i-=4)
{
ans += a[i] * a[i];
}
cout << ans << "\n";
return 0;
}
```
:::
## pC 在島嶼圈養的動物 (Animals in captivity on the island)
**Prepared By Colten**
我們首先可以先觀察到題目的範圍 $1 \le n,m \le 50$,數值範圍特別小,因此本題主要是在考大家實作的細心與耐心程度,我們可以先利用一個迴圈先枚舉所有的可行的正方形邊長,接著利用此邊長開始在地圖上枚舉所有可能,直到找到其中一個正方形區域當中,都是陸地,沒有海水,則代表此區域是可以架設柵欄的,則輸出答案,特別注意的是:由於可能會有很多種答案,因此題目有說明最佳解的形式,所以在枚舉時要記得考量到這點,去做枚舉順序上的改變。
時間複雜度:$O(N^5)$
:::spoiler 參考解法 (C++)
```cpp=
#include <bits/stdc++.h>
#define int long long
using namespace std;
int mp[105][105];
signed main(void)
{
ios_base::sync_with_stdio(false);
cin.tie(0);
int n,m;
cin >> n >> m;
for(int i=0;i<n;i++)
{
for(int k=0;k<m;k++)
{
cin >> mp[i][k];
}
}
for(int u=min(n,m);u>=3;u--)
{
for(int i=0;i<n;i++)
{
for(int k=0;k<m;k++)
{
if( i + u > n || k + u > m ) continue;
bool square_check = true;
for(int t1=i;t1<i+u;t1++)
{
for(int t2=k;t2<k+u;t2++)
{
if( mp[t1][t2] == 0 )
{
square_check = false;
break;
}
if( square_check == false ) break;
}
}
if( square_check == true )
{
for(int t1=i;t1<i+u;t1++)
{
if( t1 != i && t1 != i + u - 1 )
{
mp[t1][k] = 2;
mp[t1][k+u-1] = 2;
continue;
}
for(int t2=k;t2<k+u;t2++)
{
mp[t1][t2] = 2;
}
}
cout << "YES\n";
for(int t1=0;t1<n;t1++)
{
for(int t2=0;t2<m;t2++)
{
cout << mp[t1][t2];
if( t2 != m - 1 ) cout << " ";
}
cout << "\n";
}
exit(0);
}
}
}
}
cout << "IMPOSSIBLE\n";
return 0;
}
```
:::
## pD 球與管子 (Tube of balls)
**Prepared By Fysty**
假設從左邊放入的球共 $l$ 顆,依序是編號 $x_1,x_2,...,x_l$.
因為沒有任何球放不下,所以隔板以左一定恰有 $l$ 顆球。
因此編號 $x_i$ 的球一定會停在空間 $i$.
從右邊放入的球作法類似,就只是相反而已。
以上可以用陣列實作,也可以用 stl::deque,左右端放入分別對應 push_front() 跟 push_back()。
每個子測資的時間複雜度為 $O(n)$.
:::spoiler 參考解法 (C++)
```cpp=
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<ll,ll> pll;
#define MottoHayaku ios::sync_with_stdio(false);cin.tie(0);
#define rep(i,n) for(int i=0;i<n;i++)
#define rep1(i,n) for(int i=1;i<=n;i++)
#define pb push_back
#define F first
#define S second
const int MOD=1e9+7;
const ll INF=2e18;
int main()
{
MottoHayaku
ll t;
cin>>t;
while(t--)
{
ll n;
cin>>n;
deque<ll> dq;
rep(i,n)
{
ll a;
char c;
cin>>a>>c;
if(c=='L') dq.push_front(a);
else dq.push_back(a);
}
while(!dq.empty())
{
cout<<dq.front()<<" ";
dq.pop_front();
}
cout<<"\n";
}
}
```
:::
## pE 最高價值的交換 (Highest value exchange)
**Prepared By Colten**
我們可以先注意到的是,本題主要的操作如下:
- 我們必須要從一個序列當中,拿出最大值
- 我們必須要從一個序列當中,移除最大值
- 我們每新增一次的元素之後,就必須更新目前這個序列的最大值
綜合以上幾點,我們可以利用 STL-priority_queue 來幫我們有效率地完成這些操作。
當然也可以使用 STL-multiset 來完成這些操作,不過以下提供 priority_queue 的作法
時間複雜度:$O(N\log N)$
:::spoiler 參考解法 (C++)
```cpp=
#include <bits/stdc++.h>
#define int long long
using namespace std;
priority_queue <int> pq1,pq2;
signed main(void)
{
ios_base::sync_with_stdio(false);
cin.tie(0);
int n;
cin >> n;
int ans1 = 0,ans2 = 0;
while(n--)
{
int a,b,c;
cin >> a >> b >> c;
pq1.push(a);
pq2.push(b);
if( c == 1 )
{
int u1 = pq1.top(),u2 = pq2.top();
ans1 += u2;
ans2 += u1;
pq1.pop();
pq2.pop();
pq1.push(u2);
pq2.push(u1);
}
}
cout << ans1 << " " << ans2 << "\n";
return 0;
}
```
:::
## pF 多項式大師 (Master of Polynomials)
**Prepared By Fysty**
當我們除以 $a$ 的時候,要轉成乘以 $a^{-1}\bmod p$,$a^{-1}$ 為 $a$ 的模反元素,而由費馬小定理我們知道 $a^{-1}\equiv a^{-1}\cdot a^{p-1}\equiv a^{p-2}\pmod p$.
我們提供三種構造方法:
**Construction 1** (時間複雜度 $O(p^3)$)
將 $0,1,...,p-1$ 代入 $f(x)$ 會得到 $p$ 個 $p$ 元一次方程式。
所以我們可以用高斯消去法解出 $a_0,a_1,...,a_{p-1}$,注意除以一個數要看成乘以該數的模反元素。
這個方法的複雜度是 $O(p^3)$,只夠過前幾個子題。
**Construction 2** (時間複雜度 $O(p^2)$)
我們利用拉格朗日插值法。
令 $P(x)=x(x-1)(x-2)\cdots(x-p+1)$.
則 $f(x)=\sum_{k=0}^{p-1}\frac{\frac{P(x)}{x-k}}{\prod\limits_{l\neq k}(k-l)}$
若 $P(x)$ 的係數一開始先用 $O(p^2)$ 的 dp 求出。
則對於每個 $k$,$\frac{P(x)}{x-k}$ 的係數可以利用多項式除法在 $O(p\log p)$ 內算出。
令 $S_k=\prod\limits_{l\neq k}(k-l)$,則 $S_0=(-1)^{p-1}\cdot\prod\limits_{i=1}^{p-1} i$.
由 Wilson's Theorem 得 $S_0\equiv (-1)^p\pmod p$.
注意 $-1\equiv 1\pmod p$,所以 $S_0\equiv -1\pmod p$.
而 $\prod\limits_{l\neq k}(k-l)\equiv \prod\limits_{l\neq k-1}(k-1-l)\times \frac{k}{k-1-p}\equiv \prod\limits_{l\neq k-1}(k-1-l)\pmod p$
所以 $S_0\equiv S_1\equiv\cdots\equiv S_{p-1}\equiv -1\pmod p$.
最後用 $O(p^2)$ 將每一個部分加起來就得到 $f(x)$ 的係數了。
**Construction 3** (時間複雜度 $O(p^2)$)
我們再次利用費馬小定理。
考慮 $f_k(x)=r_k-r_k(x-k)^{p-1}$.
注意到當 $x\neq k$ 時,$f_k(x)\equiv r_k-r_k\equiv 0\pmod p$,而 $f_k(k)\equiv r_k\pmod p$.
因此所求 $f(x)=\sum\limits_{k=0}^{p-1} f_k(x)$.
而 $\binom{p-1}{0},\binom{p-1}{1},...,\binom{p-1}{p-1}$ 可以花 $O(p\log p)$ 事先求出。
所以每個 $f_k(x)$ 的係數可以在 $O(p)$ 內求出。
最後用 $O(p^2)$ 將全部加起來。
**Remark**
不難證明其實答案是唯一的。
:::spoiler 參考解法 (C++) (Constuction 2)
```cpp=
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<ll,ll> pll;
#define MottoHayaku ios::sync_with_stdio(false);cin.tie(0);
#define rep(i,n) for(int i=0;i<n;i++)
#define rep1(i,n) for(int i=1;i<=n;i++)
#define pb push_back
#define F first
#define S second
const int MOD=1e9+7;
const ll INF=2e18;
int main()
{
MottoHayaku
ll t;
cin>>t;
while(t--)
{
ll p;
cin>>p;
vector<ll> r(p),a(p,0),dp(p+1,0),tmp(p+1,0);
rep(i,p) cin>>r[i];
dp[0]=1;
rep1(i,p)
{
rep1(j,i) tmp[j]=(tmp[j]+dp[j-1])%p;
rep(j,i) tmp[j]=(tmp[j]+dp[j]*(p-i)%p)%p;
rep(j,i+1) dp[j]=tmp[j],tmp[j]=0;
}
rep(i,p)
{
ll c=r[i]*(p-1)%p,cur=0;
for(int j=p-1;j>=0;j--)
{
ll d=(dp[j+1]-cur+p)%p;
a[j]=(a[j]+c*d%p)%p;
cur=d*(p-i)%p;
}
}
rep(i,p) cout<<a[i]<<" ";
cout<<"\n";
}
}
```
:::
:::spoiler 參考解法 (C++) (Constuction 3)
```cpp=
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<ll,ll> pll;
#define MottoHayaku ios::sync_with_stdio(false);cin.tie(0);
#define rep(i,n) for(int i=0;i<n;i++)
#define rep1(i,n) for(int i=1;i<=n;i++)
#define pb push_back
#define F first
#define S second
const int MOD=1e9+7;
const ll INF=2e18;
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);
}
ll inv(ll a,ll m) {return fpow(a,m-2,m);}
int main()
{
MottoHayaku
ll t;
cin>>t;
while(t--)
{
ll p;
cin>>p;
vector<ll> r(p),a(p,0),c(p,1);
rep1(i,p-1) c[i]=c[i-1]*(p-i)%p*inv(i,p)%p;
rep(i,p)
{
cin>>r[i];
ll x=1;
a[0]=(a[0]+r[i])%p;
for(int j=p-1;j>=0;j--)
{
a[j]=(a[j]-c[j]*x%p*r[i]%p+p)%p;
x=x*(p-i)%p;
}
}
rep(i,p) cout<<a[i]<<" ";
cout<<"\n";
}
}
```
:::
## pG 多邊形 (Polygon)
**Prepared By LittleCube**
### 前言
這題並不是數學題,
仔細想想就會發現用排列組合列式極難,當然如果你夠電搞不好也是可行的。
感謝所有有撈分/AC的人><
### Subtask 1, 2, 3
這3個subtask是給暴搜/特判分的,基本上就直接DFS下去就好。
### Subtask 4
似乎不能再這樣暴搜下去了,所以有兩個觀察:
- 走過的線段必連續
- 實際上在哪個點不重要,重要的是左邊走了多少,右邊走了多少
所以,這樣改一下,可以列出狀態:
$dp[i][j][k] =$第 $i$ 步的時候,左邊長 $j$ 而右邊長 $k$,
這樣轉移就有一些case:
- 當 $j = 0$ 或 $k = 0$,可以多畫一條,
- 否則只有在畫過的移動這個選擇。
特別注意,當 $j + k = K$ ,表示走完了,可以開始閒晃,每走一步方法數$\times 2$。
複雜度$O(NK^2)$。
### Subtask 5
接下來可以做的事就是觀察這個DP,發現轉移可以用矩陣快速冪加速,
會是一個$k^2 + 1$的方陣,
所以複雜度$O(K^6\log N)$
但是這個矩陣其實有很多都沒辦法轉移或沒用到,
所以可以壓狀態,或是對稀疏的部分優化(800ms $\rightarrow$ 100ms)。
### Subtask 6
**Idea By dreamoon**
但有沒有發現,其實不能走到的情形比較好算?
令$F_k^{i..j}$表示從 $k$ 開始,從頭到尾都只走在 $i$ **向右走**到 $j$ 的所有邊上,
參考路線:$1 \rightarrow 2 \rightarrow 3 \rightarrow \cdots \rightarrow K - 2 \rightarrow K - 1 \rightarrow K \rightarrow 1 \rightarrow 2 \rightarrow \cdots$
所求 $= F_1^{1..K} + F_1^{K..K-1} + \dots + F_1^{2..1} - F_1^{1..K - 1} - F_1^{K..K - 2} - \cdots - F_1^{3..1}$
旋轉之後會得到:
所求 $= F_1^{1..K} + F_2^{1..K} + \dots + F_K^{1..K} - F_1^{1..K - 1} - F_2^{1..K - 1} - \cdots - F_{K - 1}^{1..K - 1}$
可以用兩次矩陣求得,
複雜度$O(K^3\log N)$
:::spoiler 參考解法 (C++)
``` cpp
#include <bits/stdc++.h>
#define ll long long
#define MOD 1000000007
using namespace std;
ll k, n, tmpmat[105][105];
void copy(ll a[105][105], ll b[105][105])
{
for (int i = 0; i <= 100; i++)
for (int j = 0; j <= 100; j++)
b[i][j] = a[i][j];
}
void mul(ll a[105][105], ll b[105][105], ll c[105][105])
{
for (int i = 0; i <= 100; i++)
for (int j = 0; j <= 100; j++)
c[i][j] = 0;
for (int i = 0; i <= 100; i++)
for (int j = 0; j <= 100; j++)
for (int p = 0; p <= 100; p++)
if (a[i][p] != 0)
if (b[p][j] != 0)
c[i][j] = (c[i][j] + a[i][p] * b[p][j]) % MOD;
}
void mtxfastpow(ll base[105][105], ll res[105][105], ll p)
{
while (p > 0)
{
if (p & 1)
{
mul(res, base, tmpmat);
copy(tmpmat, res);
}
p >>= 1;
mul(base, base, tmpmat);
copy(tmpmat, base);
}
}
ll fastpow2(ll p)
{
ll a = 1, b = 2;
while (p > 0)
{
if (p & 1)
a = a * b % MOD;
p >>= 1;
b = b * b % MOD;
}
return a;
}
ll cal(int k)
{
ll base[105][105] = {{0}}, res[105][105] = {{0}}, ans = 0;
base[1][2] = 1;
base[k][k - 1] = 1;
for (int i = 2; i < k; i++)
{
base[i][i + 1] = 1;
base[i][i - 1] = 1;
}
for (int i = 0; i <= k; i++)
res[i][i] = 1;
mtxfastpow(base, res, n);
for (int i = 1; i <= k; i++)
for (int j = 1; j <= k; j++)
ans = (ans + res[i][j]) % MOD;
return ans;
}
signed main()
{
cin >> k >> n;
cout << (fastpow2(n) - cal(k) + cal(k - 1) + MOD) % MOD << '\n';
}
```
:::
### 道歉啟事
抱歉官解壓了一點常數,以至於時限卡的有點緊,被卡常的對不起 ;w;
官解壓到常數的地方:
- 稀疏矩陣優化
- 把乘法開固定大小可以unroll-loops,常數相對較小
既然都壓常了,就順便講一下:
使用 C++ 作答的可以考慮選擇 GNU C++17 9.2.0 (64 bit, msys 2),跑比較快
(例如這題的官解這樣跑快2倍)
然後真的覺得複雜度對的時候可以開 Ofast, unroll-loops 碰碰運氣
當然可以也砸一些怪怪的矩陣乘法優化,但是官解並沒有這麼毒瘤