D. Doin' Time ——区间dp
题意:
给你长度为n的数列(n <= 300)每次可以选择一个数ax 与它后面的那个数ax+1合并成一个新的数ax*ax+1 然后得到(ax-ax+1)2 的分数 一共n-1次操作
求最多可以得到多少分数
思路:
区间dp
最后肯定是由两个数合并成一个数 就是要考虑 分成哪两段 才能使答案更优 而每一段又可以分成两段 只要找到每段最优的情况(即从小段的最优情况转移给大段) 然后输出整段最优解
预处理 利用前缀积 和 逆元 方便处理后续数据
#include<iostream> #include<algorithm> #include<string> #include<set> #include<map> #include<cstdio> #include<cmath> #include<cstring> #include<queue> #include<stack> #include<unordered_map> #include<iomanip> #define ll long long #define ull unsigned long long #define IOS ios::sync_with_stdio(false);cin.tie(nullptr);cout.tie(nullptr) #define m_p make_pair #define pi acos(-1) using namespace std; const int N = 2e5 + 5; const int M = 5e5 + 5; const double eps = 1e-4; const int inf = 0x3f3f3f3f; const ll INF = 0x3f3f3f3f3f3f3f3f; const ll mod = 1000003; ll n, m, k, a[310], pre[310], pre2[310], dp[310][310]; ll qsm(ll a, ll b){ int ans = 1; while(b){ if(b & 1) ans = ans * a % mod; a = a * a % mod; b >>= 1; } return ans; } void solve() { cin >> n; for(int i = 1; i <= n; i++){ cin >> a[i]; } pre[0] = pre2[0] = 1; for(int i = 1; i <= n; i++){ //前缀积 pre[i] = pre[i - 1] * a[i] % mod; //逆元 即 前缀积的倒数 pre2[i] = qsm(pre[i], mod - 2); } ll l, r; //i代表区间长度 从小到大枚举长度 然后进行状态转移 for(int i = 2; i <= n; i++){ //相同长度的不同段的最优情况 for(int j = 1; j + i - 1 <= n; j++){ //左右区间 有区间不能超界 l = j, r = i + j - 1; //枚举K 中间端点 即这个区间从哪两个更短的区间转移过来最优 for(int k = l; k <= r - 1; k++){ dp[l][r] = max(dp[l][r], dp[l][k] + dp[k + 1][r] + (pre[k] * pre2[l - 1] % mod - pre[r] * pre2[k] % mod) * (pre[k] * pre2[l - 1] % mod - pre[r] * pre2[k] % mod)); } } } cout << dp[1][n] << "\n"; } signed main() { IOS; ll t = 1; //cin >> t; while(t--) solve(); return 0; }