「JSOI2011」柠檬

传送门
Luogu

解题思路

还是一道斜率优化题。
在优化前,还有一个值得一提的优化:
对于最后的最有分割方案,每一段的两个端点一定是同颜色的,并且作为这一段的 \(s_0\)
证明:如果不作为这一段的 \(s_0\),那么它显然没有贡献,把这一个单独分出来显然更优,直到最后两个端点就一定都是 \(s_0\) ,颜色相同。
那么我们只需要从之前和该点种类相同的位置进行转移即可。
这样就从直接枚举的复杂度 \(O(n^3)\) 优化到了 \(O(n^2)\) ,但还是不够,继续考虑优化。
我们先把转移方程写出来:
\(dp_i\) 表示把前 \(i\) 个取完,且 \(i\) 点作为一段的终点最大收益。
\[dp_i=\max\limits_{1\le j \le i,a_j=a_i}\left\{dp_{j-1}+s_i(p_i-p_j+1)^2\right\}\]
\(p_i\) 表示第 \(i\) 个点是种类为 \(s_i\) 的第 \(p_i\) 个点。
根据斜率优化的一些做法,我们可以把式子化成这样:
\(p_i\times 2p_js_j+dp_i-s_i(p_i+1)^2=dp_{j-1}-2a_jp_j+a_jp_j^2\)
\(x_i = 2s_ip_i,y_i=dp_{i-1}-2s_ip_i+s_ip_i^2\)
\(p_ix_j+dp_i-s_i(p_i+1)^2=y_j\)
因为要让 \(dp_i\) 最大化,所以我们对每一种颜色都用单调栈维护一个上凸包,这样才能满足决策单调性。
注意一点细节:
因为我们的转移点 \(j\) 的范围是 \([1,i]\) 的,而我们再插入 \(j\) 这个点时只有 \(dp_{j-1}\) 这个信息,为了能取到 \(dp_{i-1}\) ,我们需要在寻找最优转移点之前就把 \(i\) 加入单调栈。

细节注意事项

  • 咕咕咕

参考代码

#include <algorithm>
#include <iostream>
#include <cstring>
#include <cstdlib>
#include <cstdio>
#include <cctype>
#include <cmath>
#include <ctime>
#include <vector>
#define int long long 
#define rg register
using namespace std;
template < typename T > inline void read(T& s) {
    s = 0; int f = 0; char c = getchar();
    while (!isdigit(c)) f |= (c == '-'), c = getchar();
    while (isdigit(c)) s = s * 10 + c - 48, c = getchar();
    s = f ? -s : s;
}

const int _ = 100010;

int n, a[_], p[_], pos[_];
int dp[_];
vector < int > stk[_];

inline int X(int i) { return 2 * a[i] * p[i]; }

inline int Y(int i) { return dp[i - 1] - 2 * a[i] * p[i] + a[i] * p[i] * p[i]; }

inline double slope(int i, int j) { return (double) (Y(j) - Y(i)) / (X(j) - X(i)); }

#define A stk[t][stk[t].size() - 2]
#define B stk[t][stk[t].size() - 1]

signed main() {
#ifndef ONLINE_JUDGE
    freopen("in.in", "r", stdin);
#endif
    read(n);
    for (rg int i = 1; i <= n; ++i) read(a[i]), p[i] = ++pos[a[i]];
    for (rg int i = 1; i <= n; ++i) {
        int t = a[i];
        while (stk[t].size() >= 2 && slope(A, B) < slope(B, i)) stk[t].pop_back();
        stk[t].push_back(i);
        while (stk[t].size() >= 2 && slope(A, B) < p[i]) stk[t].pop_back();
        int j = stk[t].back();
        dp[i] = dp[j - 1] + a[i] * (p[i] - p[j] + 1) * (p[i] - p[j] + 1);
    }
    printf("%lld\n", dp[n]);
    return 0;
}

完结撒花 \(qwq\)

猜你喜欢

转载自www.cnblogs.com/zsbzsb/p/11746552.html