Loading

【题解】P5574 [CmdOI2019]任务分配问题

sto cmd 学长 orz

题意

P5574 [CmdOI2019]任务分配问题

给定一个长度为 \(n\) 的排列,试将它分成 \(k\) 段,使得每段的顺序对数量之和最小。

\(n \leq 2.5 \times 10^4, k \leq 25\)

思路

决策单调性优化 dp + 分治乱搞。

dp 做法是显然的:

\(f[i][j]\) 表示前 \(i\) 个数分成 \(j\) 段的最小代价。

\(c(i, j)\) 表示 \([i, j]\) 内的顺序对数量,则 \(f[i][j] = \min\limits_{k = 0}^{i - 1} f[k][j - 1] + c(k + 1, i)\)

这个方程满足四边形不等式,感性理解:

\(p1 \leq p2 \leq p3 \leq p4\),观察四边形不等式:

\(c(p1, p3) + c(p2, p4) \leq c(p1, p4) + c(p2, p3)\)

\(s(l, r, L, R)\) 表示左端点在 \([l, r]\) 且右端点在 \([L, R]\) 的顺序对数量,则:

\(c(p1, p4) - c(p1, p3) = s(p1, p3, p3 + 1, p4) + s(p3 + 1, p4, p3 + 1, p4)\),记为 \(\Delta1\)

\(c(p2, p4) - c(p2, p3) = s(p2, p3, p3 + 1, p4) + s(p3 + 1, p4, p3 + 1, p4)\),记为 \(\Delta2\)

那么 \(\Delta1 - \Delta2 = s(p1, p3, p3 + 1, p4) - s(p2, p3, p3 + 1, p4) \geq 0\)

所以四边形不等式成立。

观察发现是 2D/1D 类型的 dp,于是可以考虑单调性分治优化。

问题在于求出 \(c\).

区间数顺序对单次查询可以考虑用树状数组,区间做在不考虑复杂度的情况下可以莫队。然后——

因为分治树的人类智慧性质,直接在分治的时候跑类似莫队状物的复杂度是对的。

void solve(int l, int r, int L, int R)
{
    if (l > r) return;
    int mid = (l + r) >> 1;
    int cost = inf, pos = -1;
    for (int i = min(R, mid - 1); i >= L; i--)
    {
        modify(i + 1, mid);
        if (g[i] + cw < cost) cost = g[i] + cw, pos = i;
    }
    f[mid] = cost;
    solve(l, mid - 1, L, pos);
    solve(mid + 1, r, pos, R);
}

上面的代码中 modify(i + 1, mid); 代表将莫队的左右端点分别移动到 \(i + 1\)\(mid\)。观察一下,你发现在这里的复杂度是区间长度乘以 \(\log\). 对于下面的递归,递归到左半的复杂度另算,递归到右半时指针会整体向右挪动一次,但是复杂度同上。

于是按普通单调性分治复杂度的证法,这里摊下来只是多一只 \(\log\) 的问题!!!1

时间复杂度 \(O(nk \log^2 n)\)

代码

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

const int maxn = 2.5e4 + 5;
const int maxk = 30;
const int inf = 0x3f3f3f3f;

inline int min(const int &a, const int &b) { return (a <= b ? a : b); }

int n, k;
int cl, cr, cw;
int a[maxn];
int f[maxn], g[maxn];

namespace BIT
{
    int c[maxn];

    int lowbit(int x) { return x & (-x); }

    void update(int p, int w) { for (int i = p; i <= n; i += lowbit(i)) c[i] += w; }

    int query(int p)
    {
        int res = 0;
        for (int i = p; i; i -= lowbit(i)) res += c[i];
        return res;
    }
}

void modify(int tl, int tr)
{
    while (cl > tl)
    {
        cw += (cr - cl + 1 - BIT::query(a[--cl]));
        BIT::update(a[cl], 1);
    }
    while (cr < tr)
    {
        cw += BIT::query(a[++cr] - 1);
        BIT::update(a[cr], 1);
    }
    while (cl < tl)
    {
        cw -= (cr - cl + 1 - BIT::query(a[cl++]));
        BIT::update(a[cl - 1], -1);
    }
    while (cr > tr)
    {
        cw -= BIT::query(a[cr--] - 1);
        BIT::update(a[cr + 1], -1);
    }
}

void solve(int l, int r, int L, int R)
{
    if (l > r) return;
    int mid = (l + r) >> 1;
    int cost = inf, pos = -1;
    for (int i = min(R, mid - 1); i >= L; i--)
    {
        modify(i + 1, mid);
        if (g[i] + cw < cost) cost = g[i] + cw, pos = i;
    }
    f[mid] = cost;
    solve(l, mid - 1, L, pos);
    solve(mid + 1, r, pos, R);
}

int main()
{
    scanf("%d%d", &n, &k);
    for (int i = 1; i <= n; i++) scanf("%d", &a[i]);
    for (int i = 1; i <= n; i++)
    {
        BIT::update(a[i], 1);
        g[i] = g[i - 1] + BIT::query(a[i] - 1);
    }
    memset(BIT::c, 0, (n + 1) * sizeof(int));
    if (k == 1)
    {
        printf("%d\n", g[n]);
        return 0;
    }
    cl = 1, cr = 0;
    for (int i = 2; i <= k; i++)
    {
        solve(i, n, i - 1, n);
        memcpy(g, f, (n + 1) * sizeof(int));
    }
    printf("%d\n", f[n]);
    return 0;
}
posted @ 2022-12-31 10:27  kymru  阅读(58)  评论(0编辑  收藏  举报