CodeForces 868F Yet Another Minimization Problem(决策单调性优化 + 分治)

题意

给定一个序列 \(\{a_1, a_2, \cdots, a_n\}\),要把它分成恰好 \(k\) 个连续子序列。

每个连续子序列的费用是其中相同元素的对数,求所有划分中的费用之和的最小值。

\(2 \le n \le 10^5, 2 \le k \le \min(n, 20), 1 \le a_i \le n\)

题解

\(k\) 比较小,可以先考虑一个暴力 \(dp\) 。

令 \(dp_{k, i}\) 为前 \(i\) 个数划分成 \(k\) 段所需要的最小花费。

那么转移如下
\[
dp_{k, i} = \min_{j \le i} dp_{k - 1, j - 1} + w_{j, i}
\]

其中 \(w_{j, i}\) 为 \(j \sim i\) 这段划分出来需要的花费,也就是 \([j, i]\) 区间内相同元素对数。

暴力做是 \(O(n^2 k)\) 的,无法通过。

说到最优区间划分,我就想起了决策单调性,今年下半年

至于为什么满足决策单调?考虑证明 \(\mathrm{1D/1D}\) 上的 四边形不等式。具体证明可以参考此处

我们现在只有一个问题了, 就是如何快速求出 \(w_{j, i}\) 。

可以考虑把序列分块,然后预处理块到块的答案以及点到一个块的答案,然后再算算边角。

然后这个配合 二分+单调栈 可以做到 \(O(nk \sqrt n \log n)\) ,还是过不去。

对于这种分层 \(dp\) 来说,分治的复杂度就可以正确,因为每次不需要先分治左区间再算右区间,可以扫完整个区间得到 \(mid\) 的最优决策点,然后就可以把 \([l, mid)\) 和 \((mid, r]\) 的决策点分开了。

这样单次求解的话,每层是 \(O(n)\) 的,那么复杂度是 \(O(n \log n)\) 的。

然后此时我们就可以很好的算 \(w_{j, i}\) 了,要怎么算呢?

可以暴力一点做,考虑类似莫队那样维护当前计算区间的 \([l, r]\) ,然后看接下来要算的 \([l', r']\) 的相对位置,就可以得到相应的区间的花费了。

复杂度?其实是对的。具体原因可以参考非指针移动的那种做法,每次只会移动当前区间长度的指针。

这个其实是一样的,因为每次需要利用的相邻两个区间是一样的,这种移动方法的复杂度是平面上两点的曼哈顿距离,显然不会更劣。

那么最后复杂度就是 \(O(nk \log n)\) 的,似乎我的写法跑的挺快?

代码

很好写啊qwq

#include <bits/stdc++.h>

#define For(i, l, r) for (register int i = (l), i##end = (int)(r); i <= i##end; ++i)
#define Fordown(i, r, l) for (register int i = (r), i##end = (int)(l); i >= i##end; --i)
#define Rep(i, r) for (register int i = (0), i##end = (int)(r); i < i##end; ++i)
#define Set(a, v) memset(a, v, sizeof(a))
#define Cpy(a, b) memcpy(a, b, sizeof(a))
#define debug(x) cout << #x << ": " << (x) << endl

using namespace std;

typedef long long ll;

template<typename T> inline bool chkmin(T &a, T b) { return b < a ? a = b, 1 : 0; }
template<typename T> inline bool chkmax(T &a, T b) { return b > a ? a = b, 1 : 0; }

inline int read() {
    int x(0), sgn(1); char ch(getchar());
    for (; !isdigit(ch); ch = getchar()) if (ch == '-') sgn = -1;
    for (; isdigit(ch); ch = getchar()) x = (x * 10) + (ch ^ 48);
    return x * sgn;
}

void File() {
#ifdef zjp_shadow
    freopen ("F.in", "r", stdin);
    freopen ("F.out", "w", stdout);
#endif
}

const int N = 2e5 + 1e3;

int n, k, a[N], times[N];

int l, r; ll res, dp[25][N];

void Move(int L, int R) {
    while (l > L) res += times[a[-- l]] ++;
    while (l < L) res -= -- times[a[l ++]];
    while (r > R) res -= -- times[a[r --]];
    while (r < R) res += times[a[++ r]] ++;
}

void Divide(int k, int l, int r, int dl, int dr) {
    if (l > r) return;
    int mid = (l + r) >> 1, dmid = dl;
    dp[k][mid] = 0x3f3f3f3f3f3f3f3f;
    For (i, dl, min(mid, dr)) {
        Move(i, mid);
        if (chkmin(dp[k][mid], dp[k - 1][i - 1] + res)) dmid = i;
    }
    Divide(k, l, mid - 1, dl, dmid);
    Divide(k, mid + 1, r, dmid, dr);
}

int main () {

    File();

    n = read(); k = read();

    For (i, 1, n) a[i] = read();

    For (i, 1, n)
        dp[1][i] = (res += times[a[i]] ++);
    res = 0; Set(times, 0);

    l = 1; r = 0;
    For (i, 2, k) Divide(i, 1, n, 1, n);
    printf ("%lld\n", dp[k][n]);

    return 0;

}

原文地址:https://www.cnblogs.com/zjp-shadow/p/10278994.html

时间: 2024-11-15 22:16:38

CodeForces 868F Yet Another Minimization Problem(决策单调性优化 + 分治)的相关文章

Codeforces 868F. Yet Another Minimization Problem【决策单调性优化DP】【分治】【莫队】

LINK 题目大意 给你一个序列分成k段 每一段的代价是满足\((a_i=a_j)\)的无序数对\((i,j)\)的个数 求最小的代价 思路 首先有一个暴力dp的思路是\(dp_{i,k}=min(dp_{j,k}+calc(j+1,i))\) 然后看看怎么优化 证明一下这个DP的决策单调性: trz说可以冥想一下是对的就可以 所以我就不证了 (其实就是决策点向左移动一定不会更优) 然后就分治记录当前的处理区间和决策区间就可以啦 //Author: dream_maker #include<bi

CodeForces - 868F Yet Another Minimization Problem

Description 给定一个长度为 \(n(n\le 10^5)\) 的数列,第 \(i\) 个数是 \(a_i\in[1,n]\) ,要求将其划分为 \(k(2\le k\le min(20,n))\) 段以后每段价值和最小. 定义一段的价值为该段相同数的数对个数. Solution 定义 \(calc(l,r)\) 为 \([l,r]\) 这一段的价值, \(dp[j][i]\) 为前 \(i\) 个数划分为 \(j\) 段的最小价值.那么显然有 \[ dp[j][i]=min\{dp[

P3515 [POI2011]Lightning Conductor[决策单调性优化]

给定一序列,求对于每一个$a_i$的最小非负整数$p_i$,使得$\forall j \neq i $有$ p_i>=a_j-a_i+ \sqrt{|i-j|}$. 绝对值很烦 ,先分左右情况单独做.现在假设j都在i左边,则$p_i=max{a_j-a_i+ \sqrt{i-j}}=max{a_j+ \sqrt{i-j} }-a_i$.带根号,不易斜率优化,考虑证决策单调性. 假设最优决策为j,j之前的任意决策称之为$j'$,则有 $f[j]+\sqrt{i-j} \geqslant f[j']

决策单调性优化dp 专题练习

决策单调性优化dp 专题练习 优化方法总结 一.斜率优化 对于形如 \(dp[i]=dp[j]+(i-j)*(i-j)\)类型的转移方程,维护一个上凸包或者下凸包,找到切点快速求解 技法: 1.单调队列 : 在保证插入和查询的x坐标均具有单调性时可以使用 2.单调栈+二分:保证插入有单调性,不保证查询有单调性 3.分治+ 1 或 2:在每次分治时将\([l,mid]\)这段区间排序后插入,然后更新右区间\([mid+1,r]\)的答案 二.分治.单调队列维护有单调性的转移 (甚至还有分治套分治)

【CodeForces】868F. Yet Another Minimization Problem

原题链接 题目大意是有N个数,分成K段,每一段的花费是这个数里相同的数的数对个数,要求花费最小 如果只是区间里相同数对个数的话,莫队就够了 而这里是!边单调性优化边莫队(只是类似莫队)!而移动的次数和分治的复杂度是一样的! 这个时候就不能用单调栈+二分了,得用分治 分治的方法就是\(Solve(l,r,ql,qr)\)表示我想计算区间\([l,r]\)的答案,然后转移过来的区间在\([ql,qr]\) 暴力计算出\(f[mid]\)的值,找到最优转移点是\(k\),然后分成\(Solve(l,m

Bzoj 1563: [NOI2009]诗人小G(决策单调性优化)

原题面 带有详细证明的转这里 题意:每一个线段有一个长度,有一个标准长,现在要把这些线段按照顺序分行,每行的不和谐值等于标准长和该行线段总长的差的绝对值的p次方.现在要求最小的不和谐值之和. 开始的时候完全读错题了,以为p==2 for ever.真是太天真.后来看数据范围才发现.我真是面向数据编程? n^2的dp是一眼秒的.然后如果是p=2,那就是一个非常像玩具装箱的斜率优化dp.对于4.5的数据范围.可以想到用贪心优化dp.因为每一行的长度不会通过拼接线段(线段条数>=2)达到2*标准长,这

bzoj2216: [Poi2011]Lightning Conductor(分治决策单调性优化)

每个pi要求 这个只需要正反DP(?)一次就行了,可以发现这个是有决策单调性的,用分治优化 #include<iostream> #include<cstring> #include<cstdlib> #include<cstdio> #include<cmath> #include<algorithm> using namespace std; const int maxn=500010,inf=1e9; int n; int a[

题解——[NOI2009]诗人小G 决策单调性优化DP

第一次写这种二分来优化决策单调性的问题.... 调了好久,,,各种细节问题 显然有DP方程: f[i]=min(f[j] + qpow(abs(sum[i] - sum[j] - L - 1))); 其中f[i]代表到了第i个句子的最小答案 qpow用于处理^p sum为前缀和 (同时为了处理句子之间的空格问题,我们在统计前缀和的时候就默认在句子后面加一个空格, 然后在计算的时候,由于每一行只有最后一个不用加空格,直接减掉这个多加的空格即可获得正确长度) 首先我们可以打表发现是满足决策单调性的,

决策单调性优化dp

决策单调性: 对于一些dp方程,经过一系列的猜想和证明,可以得出,所有取的最优解的转移点(即决策点)位置是单调递增的. 即:假设f[i]=min(f[j]+b[j]) (j<i) 并且,对于任意f[i]的决策点g[i],总有f[i+1]的决策点g[i+1]>=g[i](或者<=g[i]) 那么,这个方程就具备决策单调性. 这个有什么用吗? 不懂具体优化方法的话确实也没有什么用.可能还是n^2的.只不过范围可能少了一些. 经典入门例题: Description: [POI2011]Ligh