Loading

【题解】LOJ #6384. 「是男人就过8题——Pony.ai」SignLocation

题意

LOJ #6384. 「是男人就过8题——Pony.ai」SignLocation

给定 \(n\) 个整点 \(p_1, ..., p_n\) 以及 \(k\) 次标记点的机会,定义 \(c(i, j)\) 为:

  1. \(i\) 个整点和第 \(j\) 个整点之间存在标记点,则 \(c(i, j)\) 为距离 \(i\) 最近的标记点与 \(i\) 的坐标之差。

  2. 反之为 \(|p_i - p_j|\)

注意标记点不一定在整点上。

\(\sum\limits_{i \neq j} c(i, j)\) 的最小值。

\(0 \leq k \leq 200, 1 \leq n \leq 10000, p_i \leq 10^7\)

思路

决策单调性优化 dp.

首先可以用反证法证明标记整点最优。

于是可以考虑设 \(f[i][j]\) 表示已经标记 \(i\) 个整点,最后一个标记的是第 \(j\) 个整点时已经产生的最小贡献。

容易发现选出的 \(k\) 个标记点会将值域分成 \(k + 1\) 个区间(可能为空),不妨令 \(\sum\limits_{i \neq j} c(i, j)\) 算作是 \(i\) 所在区间的贡献。

假设当前最后一个标记点在 \(r\),倒数第二个标记点在 \(l\),那么转移时只需要考虑区间 \((l, r)\) 的贡献。

分类讨论推一下式子:

  1. \(l < j < r\)
    此时 \((i, j)\) 之间没有其他的标记点,于是贡献为坐标之差。
    \(\sum\limits_{i \neq j} c(i, j) = 2 \sum\limits_{p = l + 2}^{r - 1} \sum\limits_{q = l + 1}^{r - 1} (x_p - x_q)\)
    考察每一个下标的出现次数,可以化简上式得:
    \(原式 = \sum\limits_{p = l + 2}^{r - 1} x_p (p - l - 1) - \sum\limits_{q = l + 1}^{r - 2} x_q (r - q - 1)\)

  2. \(j \leq l\)
    此时距离 \(i\) 最近的标记点为 \(l\)
    \(\sum\limits_{i \neq j} c(i, j) = \sum\limits_{p = l + 1}^{r - 1} (x_p - x_l)l\)
    同理化简得 \(原式 = (\sum\limits_{p = l + 1}^{r - 1} x_p l) - x_l (r - l - 1) l\)

  3. \(j \geq r\)
    此时距离 \(i\) 最近的标记点为 \(r\)
    \(\sum\limits_{i \neq j} c(i, j) = \sum\limits_{p = l + 1}^{r - 1} (x_r - x_p)(n - r + 1)\)
    化简得 \(原式 = x_r (r - l - 1) (n - r + 1) - \sum\limits_{p = l + 1}^{r - 1} x_p(n - r + 1)\)

发现只需处理 \(p_i\)\(i p_i\) 的前缀和就可以 \(O(1)\) 计算一段区间的贡献。

观察做法,发现是一个划分区间的 dp(选点等价于划分区间),于是联想到决策单调性优化 dp.

根据题解知 确实有决策单调性(具体不太会证),于是上单调性分治优化即可,时间复杂度 \(O(k n \log n)\)

代码

#include <cstdio>
#include <algorithm>
using namespace std;

typedef long long ll;

const int maxn = 1e4 + 5;
const int maxk = 2e2 + 5;
const ll inf = 1e18;

int n, k;
int p[maxn];
ll s1[maxn], s2[maxn];
ll f[maxk][maxn];

ll get1(int l, int r)
{
    ll ans = 2 * (s2[r - 1] - s2[l + 1] + s2[r - 2] - s2[l] - (s1[r - 1] - s1[l + 1]) * (l + 1) - (s1[r - 2] - s1[l]) * (r - 1));
    ans += 1ll * l * (s1[r - 1] - s1[l] - 1ll * p[l] * (r - l - 1));
    ans += 1ll * (n - r + 1) * (1ll * p[r] * (r - l - 1) - s1[r - 1] + s1[l]);
    return ans;
}

ll get2(int l, int r)
{
    if (l == r) return 0;
    ll ans = 2ll * (s2[r] - s2[l + 1] + s2[r - 1] - s2[l] - (s1[r] - s1[l + 1]) * (l + 1) - (s1[r - 1] - s1[l]) * r);
    ans += 1ll * l * (s1[r] - s1[l] - 1ll * p[l] * (r - l));
    return ans;
}

void solve(int cur, int l, int r, int vl, int vr)
{
    if (l > r) return;
    int mid = (l + r) >> 1, pos = l;
    ll res = inf;
    for (int i = min(vr, mid - 1); i >= vl; i--)
    {
        ll cost = f[cur - 1][i] + get1(i, mid);
        if (cost < res) res = cost, pos = i;
    }
    f[cur][mid] = res;
    solve(cur, l, mid - 1, vl, pos);
    solve(cur, mid + 1, r, pos, vr);
}

int main()
{
    while (~scanf("%d%d", &n, &k))
    {
        k = min(k, n);
        for (int i = 1; i <= n; i++)
        {
            scanf("%d", &p[i]);
            s1[i] = s1[i - 1] + p[i];
            s2[i] = s2[i - 1] + 1ll * p[i] * i;
        }
        if (k == 0)
        {
            ll ans = 0;
            for (int i = 1; i <= n; i++) ans += (1ll * p[i] * i - s1[i]);
            printf("%lld\n", (ans << 1ll));
            continue;
        }
        for (int i = 1; i <= k; i++)
        {
            if (i == 1) solve(1, 1, n, 0, 0);
            else solve(i, i, n, i - 1, n - 1);
        }
        ll ans = inf;
        for (int i = k; i <= n; i++) ans = min(ans, f[k][i] + get2(i, n));
        printf("%lld\n", ans);
    }
    return 0;
}
posted @ 2022-12-30 07:10  kymru  阅读(87)  评论(0编辑  收藏  举报