CF2074G 题解
CF2074G 题解
区间DP
题意
有一个正 \(n\) 边形,可以进行若干次操作,每一次操作,可以从中选取 \(3\) 个任意的没有被选择过的点,组成一个三角形,单次得分为这三个顶点权值的乘积,并且任意三角形不能有重叠的区域。
分析
这个应该很一眼的区间 DP,不过和传统的 \(O(n^3)\) 区间 DP 也不大一样,暴力转移是 \(O(n^3)\) ,也就是说一共需要 \(O(n^5)\) 的时间。
然后我就不会做了。
这个非常朴素的转移是 \(dp[L,R]=\max_{L≤i<j<k≤R}(dp[L,i−1]+dp[i+1,j−1]+dp[j+1,k−1]+dp[k+1,R]+a_i\times a_j\times a_k)\)
。也就是从这个区间里面选三个点出来拼一个三角形,然后剩下的所有区间加起来。
Key point
DP 的优化在于减少重复计算,找到最优决策点,或者说套个数据结构优化一下找最大最小值的过程。
当然我是看了题解才意识到的。
1
当 \(L<i<j<k<R\) 的时候,转移方程中 \(dp[i+1,k−1]+a_i\times a_j\times a_k)\) 的部分实际上可以换成 \(dp[i,k]\) ,这样一定不会使得这个答案变得更劣,因为当 \(dp[i+1,k−1]+a_i\times a_j\times a_k\) 这个项是被包括在 DP 的定义里面的。因此这整个一部分都可以换成
2
当 \(L=i,k=R\) 的时候 ,这时候就变成了朴素的 \(O(n)\) 区间 DP 转移,只需要从中间选出一个点即可。
(注:不要担心如果这个“项”不是 \([i,k]\) 区间的最大值怎么办,这样的话,DP 转移方程仍然会算出正确的值)
3
然而仍然会发现 1 中指出的转移是 \(O(n^2)\) 的,再思考一下,1 实际上对应着“不再多选择三角形,而是直接把三个小区间拼起来”的转移情况,运用我们刚刚已经使用过的优化思维,是不是这个 \(O(n^2)\) 的转移和 \(O(n)\) 的转移没有差异?换句话说,一定有下式成立:
并且合并其中任意两个都不会改变式子的正确性,这个用反证法可以很轻松地证明。
而且,这个题并不需要考虑环的情况,因为选取的三个点并不存在一个先后关系。
Code
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=500;
ll a[N<<1];
int n;
inline void checkmx(ll &x,ll v){if(v>x)x=v;}
inline void solve()
{
cin>>n;
for(int i=1;i<=n;++i)cin>>a[i];
vector<vector<ll>> dp(n+2,vector<ll>(n+2,0));
for(int len=3;len<=n;++len)
{
for(int l=1;l+len-1<=n;++l)
{
int r=l+len-1;
for(int k=l+1;k<=r;++k)checkmx(dp[l][r],dp[l][k-1]+dp[k][r]);
for(int k=l+1;k<=r-1;++k)checkmx(dp[l][r],dp[l+1][k-1]+dp[k+1][r-1]+a[l]*a[r]*a[k]);
}
}
cout<<dp[1][n]<<'\n';
}
int main()
{
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
int T;cin>>T;
while(T--)solve();
return 0;
}
思考
这道题里面反复运用的 DP 优化过程其实就是通过不失正确性的替换,减少需要枚举的量,其中需要考虑的就是 DP 值之间的包含性,这与线段树维护最大子段和里面pushup的思想其实是一致的。
也就是:
为什么只有三项?为什么不加几个\(lson.rmax,rson.lmax\) ?经过上面的分析,想必显而易见了。
inline void pushup(sgt &x,sgt &a,sgt &b)
{
x.l=a.l,x.r=b.r;
x.val=a.val+b.val;
x.lm=max(a.lm,a.val+b.lm),x.rm=max(b.rm,b.val+a.rm);
x.m=max({a.m,b.m,a.rm+b.lm});
}
本文来自博客园,作者:Hanggoash,转载请注明原文链接:https://www.cnblogs.com/Hanggoash/p/18771116

浙公网安备 33010602011771号