BZOJ4709:「JSOI2011」柠檬 – 题解

主要思路

首先发现两端并不会影响,所以可以直接做分段的线性 DP。正常思考暴力,发现每次都需要枚举颜色,非常麻烦。但是我们可以发现一个特别好的性质:每一次区间的颜色都取决于这个区间的最后一个元素,因为意识到选其他颜色时,我们大可把这个区间的右端点进行调整,使得下一个区间不会更劣。

了解到这个之后,DP 就是 \(\Theta(n^2)\) 的了,考虑进一步优化。对这个 DP 的式子进行展开:

\[ f_{j – 1} + a_i(pre_j – 1)^2 = (2a_i pre_i)(pre_j – 1) + f_i – a_i pre_i^2 \]

当然,这是已经做成直线式的 DP 式子了,设置变量 \(x = (pre_j – 1), k = (2a_i pre_i), b = f_i – a_i pre_i^2, y = f_{j – 1} + a_i(pre_j – 1)^2\)。从原式中发现平方项,且增长速度快于 \(y = x\),故具有决策单调性,所以只需要单调栈即可。加入元素之前,我们需要找到上两个决策之间的决策边界,然后判断一下决策边界是否大于新的决策边界,如果是就弹掉(因为不会前者更优)。

代码

// BZ4709.cpp
#include <bits/stdc++.h>

using namespace std;

const int MAX_N = 1e5 + 200;

typedef long long ll;

int n, ai[MAX_N], idx[MAX_N], cnt[MAX_N];
ll dp[MAX_N];
deque<int> stk[MAX_N];

ll calc(int x, ll y) { return dp[x - 1] + 1LL * ai[x] * y * y; }

int check(int x, int y)
{
    int l = 1, r = n, res = n + 1;
    while (l <= r)
    {
        int mid = (l + r) >> 1;
        if (calc(x, mid - idx[x] + 1) >= calc(y, mid - idx[y] + 1))
            r = mid - 1, res = mid;
        else
            l = mid + 1;
    }
    return res;
}

int main()
{
    scanf("%d", &n);
    for (int i = 1, x; i <= n; i++)
    {
        scanf("%d", &ai[i]), x = ai[i];
        idx[i] = ++cnt[ai[i]];
        while (stk[x].size() >= 2 && check(stk[x][stk[x].size() - 2], stk[x].back()) <= check(stk[x].back(), i))
            stk[x].pop_back();
        stk[x].push_back(i);
        while (stk[x].size() >= 2 && check(stk[x][stk[x].size() - 2], stk[x].back()) <= idx[i])
            stk[x].pop_back();
        dp[i] = calc(stk[x].back(), idx[i] - idx[stk[x].back()] + 1);
    }
    printf("%lld\n", dp[n]);
    return 0;
}

 

Leave a Reply

Your email address will not be published. Required fields are marked *