高考集训Day2
D. 花瓶
题目描述
qjd 家里有 n 个花瓶,它们排成了一排。
有的花瓶很好看,而有的花瓶不好看,于是 qjd 给每个花瓶都定义了它的美丽度 ai 。但是 qjd 不舍得把不好看的花瓶扔了,也不想变化它们的位置,于是他就打算把这些花瓶分成若干组,每组都是一个连续段。
假设 qjd 把这些花瓶分成了 k 个连续段,记 Si 表示第 i 个连续段中所有花瓶的美丽度之和,他希望最大化:S1S2 + S2S3 + …… + Sk-1Sk。
特别的,k=1 时上述结果为 0。
输入格式
第一行一个正整数 n 。
接下来一行共 n 个整数 ,注意这里 ai 可能是负数。
输出格式
共一行一个整数表示答案。
样例
样例输入
6
2 -1 4 3 -1 0
样例输出
13
CODE
暴力版本的dp
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include <bits/stdc++.h> using namespace std; typedef long long ll; const int maxn = 2003; const int mod = 998244353; const int INF = 0x7fffffff; int n, a[maxn]; ll sum[maxn], f[maxn][maxn], ans; inline int read() { int x = 0, f = 1; char ch = getchar(); while(ch > '9' || ch < '0') { if(ch == '-') { f = -1; } ch = getchar(); } while(ch >= '0' && ch <= '9') { x = (x << 1) + (x << 3) + (ch^48); ch = getchar(); } return x * f; } int main() { freopen("d.in", "r", stdin); freopen("d.out", "w", stdout); n = read(); for(int i=1; i<=n; i++) { a[i] = read(); sum[i] = sum[i-1] + a[i]; //printf("sum[%d] == %lld\n", i, sum[i]); } for(int i=0; i<=n; i++) { for(int j=1; j<=n; j++) { f[i][j] = -INF; } } for(int i=1; i<=n; i++) { for(int j=1; j<i; j++) { for(int k=0; k<j; k++) { //printf("k == %d j == %d i == %d\n", k, j, i); f[i][j] = max(f[i][j], f[j][k]+(sum[i]-sum[j])*(sum[j]-sum[k])); //printf("cmp: %lld\n", f[j][k]+(sum[i]-sum[j])*(sum[j]-sum[k])); //printf("zucheng: f[%d][%d] == %lld ji == %lld\n", j, k, f[j][k], (sum[i]-sum[j])*(sum[j]-sum[k])); //printf("f[%d][%d] == %lld\n", i, j, f[i][j]); } } } for(int i=1; i<n; i++) { ans = max(ans, f[n][i]); //printf("f[%d][%d] == %lld\n", n, i, f[n][i]); } printf("%lld", ans); return 0; }
正解版本
不过我现在还是不知道为什么不能 l<=r 作为删除条件
//我想知道l<=r的结果 //结果证明是0分。 #include <bits/stdc++.h> using namespace std; typedef long long ll; const int maxn = 5003; const int mod = 998244353; const int INF = 0x7fffffff; int n, a[maxn], id[maxn], l, r, que[maxn]; ll sum[maxn], dp[maxn][maxn]; inline int read() { int x = 0, f = 1; char ch = getchar(); while(ch > '9' || ch < '0') { if(ch == '-') { f = -1; } ch = getchar(); } while(ch >= '0' && ch <= '9') { x = (x << 1) + (x << 3) + (ch^48); ch = getchar(); } return x * f; } bool cmp(int a, int b) { return sum[a] < sum[b]; } int main() { freopen("d.in", "r", stdin); freopen("d.out", "w", stdout); n = read(); for(int i=1; i<=n; i++) { a[i] = read(); sum[i] = sum[i-1] + a[i]; id[i] = i; } //k=0可以作为决策,下面遍历要从0开始所以排序也要从0开始 sort(id, id+1+n, cmp);//按照sum从小到大的顺序排列枚举的编号 memset(dp, 0xaf, sizeof(dp)); for(int i=0; i<=n; i++) { dp[i][0] = 0; } for(int i=1; i<=n; i++) { l = 1, r = 0; //qes:为什么删队尾和删队首的操作分别要从前后循环呢? //i是定值,j是状态变量,k是决策变量,用斜率优化决策变量 //而我昨天改了一下午又循环了一遍k,充分说明我根本没理解斜率优化在干嘛 for(int j=0; j<=n; j++) { if(id[j] < i)//这时sum单调递增 { //这种形式是把除法求斜率乘过去了 //如果新数和que[r-1]的连线把r围在了下面,那么r永远不会成为最优解 //一般格式是l<=r,这里特殊注意不能相等是因为? //sum[k]越小越好?干嘛不全删光 while(l<r && (dp[i][que[r]]-dp[i][que[r-1]])*(sum[id[j]]-sum[que[r-1]]) <=(dp[i][id[j]]-dp[i][que[r-1]])*(sum[que[r]]-sum[que[r-1]])) r--; que[++r] = id[j]; } } for(int j=n; j>=0; j--) { if(id[j] > i)//这时sum单调递减 { //新数的斜率,也就是从上到下平移的那条线的斜率比l和l+1小,那么l就出局了,因为l+1会先和那条线相遇 while(l<r && (sum[id[j]]-sum[i])*(sum[que[l+1]]-sum[que[l]]) <=(dp[i][que[l+1]]-dp[i][que[l]])) l++; //操作结束,用队首更新最优解 dp[id[j]][i] = max(dp[id[j]][i], dp[i][que[l]]+(sum[id[j]]-sum[i])*(sum[i]-sum[que[l]])); } } } ll maxx = 0; for(int i=0; i<n; i++) { maxx = max(maxx, dp[n][i]); } printf("%lld", maxx); return 0; }
时光花火,水月星辰