前言:
感覺難度真沒有紫吧,因為我在模擬賽場切了耶。
題目描述:
有 \(n\) 個數,第 \(i\) 個數為 \(a_{i}\),每次可以選擇一個 \(i\) 滿足 \(a_{i}=i\),並將 \(a_{i}\) 賦值為 \(0\),最後你的得分為剩下的數的和,你希望最後得分越小越好。
給出 \(n, k\),你需要求出所有 \(a_{i} \in \left[ 0,k \right]\) 的情況下,你的得分總和與 \(10^9+7\) 取模。
解題思路 & 程式碼實現:
我的考場思路是這樣的,先思考如果給定 \(a\) 序列,如何求答案。
模擬之後發現,如果有 \(a_{i}=i\) 的話,則一定會操作 \(i\),並且每次一定會選取最前面的 \(i\) 進行操作,因為這樣一定不會影響其他可行操作位置。
繼續思考,發現每個數對答案的具體貢獻只和 後面的總運算元 有關。
假設 \(m_{i}\) 表示 \(i\) 到 \(n\) 的總運算元,那麼可以這麼分類討論:
\(a_{i} \le i\) | \(a_{i} > i\) | |
---|---|---|
貢獻 | \((m_{i+1}+a_{i}) \bmod i\) | \(m_{i+1}+a_{i}\) |
增加的操作次數 | \((m_{i+1}+a_{i})/i\) | \(0\) |
具體解釋的話,如果 \(a_{i} \le i\) 那麼它的貢獻一定是在操作一定次數當前位置後剩餘的數,反之就無法操作,只能一直加上去。
增加的操作次數也是同理。
那麼我們就可以寫出暴力程式碼:
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N = 110, mod = 1e9 + 7;
int n, k, a[N], ans;
void solve(int id){
if(id > n){
for(int i = n, m = 0; i >= 1; i--){
if(a[i] <= i) ans = (ans + (m + a[i]) % i) % mod;
else ans = (ans + m + a[i]) % mod;
if(a[i] <= i) m += (m + a[i]) / i;
}
return;
}
for(int i = 0; i <= k; i++)
a[id] = i, solve(id + 1);
}
signed main(){
// freopen("stone.in", "r", stdin);
// freopen("stone.out", "w", stdout);
cin >> n >> k;
solve(1);
cout << ans << endl;
return 0;
}
那麼我們現在會處理一個序列了,那麼可以開始思考正解了。
注意到 \(n,k \le 100\),可以發現 後面的總運算元 的數量級在 \(5 \times 10^4\) 以內。
那麼就可以很自然的設定出兩個狀態。
\(f_{i}\) 表示從後往前考慮到 \(i\) 的最小得分總和。
\(g_{i,j}\) 表示從後往前考慮到 \(i\),操作總數為 \(j\) 的方案數。
那麼轉移具體如下:
\(\begin{equation}
\begin{cases}
f_{i} = f_{i} + f_{i+1}+ \sum_{m=0}^{k} g_{i+1,m} \times ((m+j) \bmod i) & &(j \le i) \\
f_{i} = f_{i} + f_{i+1}+ \sum_{m=0}^{k} g_{i+1,m} \times (m+j) & &(j > i)\\
\end{cases}
\end{equation}\)
\(\begin{equation} \begin{cases} g_{i,m + (m + j) / i} = g_{i,m + (m + j) / i} + g_{i + 1,m}& &(j \le i) \\ g_{i,m} = g_{i,m} + g_{i + 1,m}& &(j > i) \\ \end{cases} \end{equation}\)
那麼正解程式碼如下:
#include<bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
const int N = 110, M = 5e4 + 10, mod = 1e9 + 7;
int f[N], g[N][M];
signed main(){
// freopen("stone.in", "r", stdin);
// freopen("stone.out", "w", stdout);
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
int n, k;
cin >> n >> k;
g[n + 1][0] = 1;
for(int i = n; i >= 1; i--){
for(int j = 0; j <= k; j++){//a[i]
for(int m = 0; m <= k * n; m++){//m[i+1]
if(j <= i) g[i][m + (m + j) / i] = (g[i][m + (m + j) / i] + g[i + 1][m]) % mod;
else g[i][m] = (g[i][m] + g[i + 1][m]) % mod;
}
}
for(int j = 0; j <= k; j++){
if(j <= i){
f[i] = (f[i] + f[i + 1]) % mod;
for(int m = 0; m <= k * n; m++)
f[i] = (f[i] + g[i + 1][m] * ((m + j) % i) % mod) % mod;
}else{
f[i] = (f[i] + f[i + 1]) % mod;
for(int m = 0; m <= k * n; m++)
f[i] = (f[i] + g[i + 1][m] * ((m + j) % mod) % mod) % mod;
}
}
}
cout << f[1] << endl;
return 0;
}
時間複雜度 \(O(n^2k^2)\)。