题意
LOJ #6384. 「是男人就过8题——Pony.ai」SignLocation
给定 n 个整点 p1,...,pn 以及 k 次标记点的机会,定义 c(i,j) 为:
-
第 i 个整点和第 j 个整点之间存在标记点,则 c(i,j) 为距离 i 最近的标记点与 i 的坐标之差。
-
反之为 |pi−pj|
注意标记点不一定在整点上。
求 ∑i≠jc(i,j) 的最小值。
0≤k≤200,1≤n≤10000,pi≤107
思路
决策单调性优化 dp.
首先可以用反证法证明标记整点最优。
于是可以考虑设 f[i][j] 表示已经标记 i 个整点,最后一个标记的是第 j 个整点时已经产生的最小贡献。
容易发现选出的 k 个标记点会将值域分成 k+1 个区间(可能为空),不妨令 ∑i≠jc(i,j) 算作是 i 所在区间的贡献。
假设当前最后一个标记点在 r,倒数第二个标记点在 l,那么转移时只需要考虑区间 (l,r) 的贡献。
分类讨论推一下式子:
-
l<j<r
此时 (i,j) 之间没有其他的标记点,于是贡献为坐标之差。
∑i≠jc(i,j)=2r−1∑p=l+2r−1∑q=l+1(xp−xq)
考察每一个下标的出现次数,可以化简上式得:
原式=r−1∑p=l+2xp(p−l−1)−r−2∑q=l+1xq(r−q−1)
-
j≤l
此时距离 i 最近的标记点为 l
∑i≠jc(i,j)=r−1∑p=l+1(xp−xl)l
同理化简得 原式=(r−1∑p=l+1xpl)−xl(r−l−1)l
-
j≥r
此时距离 i 最近的标记点为 r
∑i≠jc(i,j)=r−1∑p=l+1(xr−xp)(n−r+1)
化简得 原式=xr(r−l−1)(n−r+1)−r−1∑p=l+1xp(n−r+1)
发现只需处理 pi 和 ipi 的前缀和就可以 O(1) 计算一段区间的贡献。
观察做法,发现是一个划分区间的 dp(选点等价于划分区间),于是联想到决策单调性优化 dp.
根据题解知 确实有决策单调性(具体不太会证),于是上单调性分治优化即可,时间复杂度 O(knlogn)
代码
#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;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!