题目链接
快快变大 - 题目 - Daimayuan Online Judge
题目描述
给定一个长度为 n 的数组 ,接下来进行 n−1 次操作。每次选择一个下标 x ,将
和
合并成
,并且你会获得
的分数。
所以每次操作后,数组的长度将会减 1,当最后只剩下一个元素时停止操作。输出最终能获得的最大分数。
输入格式
第一行一个数字 n。
接下来一行 n 个整数 。
输出格式
一个数,表示答案。
样例输入
3
1 2 3
样例输出
26
数据规模
所有数据保证 ,
。
解题思路
区间dp模板题石子合并的变形题,合并代价即每次合并得到的分数,问题就在于每次合并后的数的计算。由于数的合并是乘法和取模运算,具有结合律的性质,因此可以提前进行预处理计算,然后O(1)进行查询,这里用到的是前缀积的方法。
令前 i 个数的积为 ,对于
区间的积,计算方法为
,但由于本题需要取模,因此就需要转换成乘法逆元再取模,即
。
然后考虑状态转移,令 表示
区间合并后的最大分数,根据区间dp的思路,可以分为
,其中
区间合并后的值和
区间合并后的值可以通过上面式子进行计算,那么合并的分数就为
,因此可以得到状态转移方程
,最后
即为答案。
AC代码
#include <bits/stdc++.h>
#define lowbit(x) (x & -x)
#define mid (l + r >> 1)
using namespace std;
typedef long long ll;
typedef pair<int, int> PII;
typedef pair<ll, ll> PLL;
const int INF = 0x3f3f3f3f;
const int mod = 1000003;
const int N = 305;
int a[N];
ll sum[N], dp[N][N];
ll quick_pow(ll a, ll b){
ll res = 1;
while(b){
if(b & 1) res = res * a % mod;
a = a * a % mod;
b >>= 1;
}
return res;
}
ll inv(ll x){
return quick_pow(x, mod - 2);
}
inline ll cal(int l, int r){
return sum[r] * inv(sum[l - 1]) % mod;
}
inline ll pow2(ll x){
return x * x;
}
int main(){
//freopen("input.txt", "r", stdin);
ios::sync_with_stdio(false);
cin.tie(NULL);
cout.tie(NULL);
int n;
cin >> n;
sum[0] = 1;
for(int i = 1; i <= n; i++){
cin >> a[i];
sum[i] = sum[i - 1] * a[i] % mod;
}
for(int len = 2; len <= n; len++){
for(int l = 1; l + len - 1 <= n; l++){
int r = l + len - 1;
for(int k = l; k < r; k++){
dp[l][r] = max(dp[l][r], dp[l][k] + dp[k + 1][r] + pow2(cal(l, k) - cal(k + 1, r)));
}
}
}
cout << dp[1][n] << endl;
return 0;
}