题意
U2FsdGVkX1+iGGcgnVGDR50qrY2/famzvdIJPHcDzBVzZJhRLBp940h3Ko01EEzw
dCJe5/clYmT54g7H2/jN11PfwmzHsyfCtE/EnJ3119dqExNBpE5hpA/UOudhV1AB
Wp4YKeE+LcFn4cAoEiDzxtuecW/dqGc6NXI8s/PKm7Q8rImznQ8UkFe41HlHylAr
hvTmL1KvuLMAkpNy1eXas90kpOW2lM5YYqixSjHkvgTzvjSFl+jVIQD2C3H5Dc/E
W3YrJqB1IWjlS5UlfQZeQ4QhZwtFPETJKxz1qysPhO7i1Iw19dT3GQUnZR6g08xL
ac3fuBQKqrLua+XZP6OHa3naTH/qtrGtsUTLoQOjqZb5qxLFftV2Jj8cnFLRf31S
F24QjiX+pb733NDW76dtIwccekiuxy9tkjVC27EsucP31HV2LO/RnLYXGjGOb2EA
WIx6UNrcQXdplDorxJ0GXm9w4p0dX+/66EJBYXghFA4nZiojXMheuon87/Bvqcsq
wK8it8lO4qMRjc3lmEXy1Y/AT4QcE28IuS44HC0tGNJhebTM8PEdFEgHKAlGOLPS
AFFrC5LIUrDsfj5kM/F+LaUSYozdUnqkTKC8gxOtUfx59A2z4YCJ4DN44v+m+8Ce
UE4hsJUF2vLaRUJzyAtgJjxCq/gKb4uLPNG0TBNDFVKNfZ0n5SM08pMVdHhkkLNd
2n7Ay589OUqqSNoZ0z6knOl26lyDCkdYGwarY+bZ67NUknajcOXDUzNkpFHOq2xV
gDsWjFTpbvDIMnLQfj04ZcH7D6/HPOKd2NdJoKFXA+8ov7iHTuDOnASMu3K0RcAt
dC18WYSrCXIj3r1UhZO+MTM2qG987vPuzxvHy0JKRXEq0Bk7t6/C3UrMQfFZcdJM
Z1JiDqEIde9wcY75J+sDGgthFMrnxIBn2ahZpo3eM7Y=
解析
先忽略掉第 次的额外代价。对于任意一对 ,如果他们在某次操作中产生了贡献,那么他们一定是本来在个连续端中,后来被分开了,所以任意一对 只会产生一次贡献。同时经过 次操作之后, 被分成 段,即任意一对 不可能在同一段,也就是每一对 已经做过了贡献。因此,分割的方法、顺序不能影响最终的贡献,且最终的贡献就是:
接下来考虑第 次的额外代价。问题转化为将长度为 的序列分割成 段,最小化上面的代价式子。设 表示分成 段,考虑前 个元素的最小代价,容易得到转移方程:
来分析一下 的性质。对于 ,可以写成:
设 。比较 和 的大小关系。考虑每一对 满足 的贡献:
- 如果 ,那么两个式子都要多上 的贡献。
- 如果 , 中有且仅有一个在 中,两个式子都多上 的贡献。
- 如果 , 中没有在 中的,则前者没有贡献,后者多 的贡献。
综上,有 ,即 满足四边形不等式。然后注意到 显然满足四边形恒等式,所以 满足四边形不等式, 满足决策单调性,可以分治计算。
最后,如何快速计算 ?可以用类似莫队的思想,容易发现,左右端点的移动次数是 级别的。
实现
constexpr int sqr(int x) { return x * x; }
int main()
{
set_io("on");
int n, k, h;
std::cin >> n >> k >> h;
std::vector<int> a(n), cc(n), cca(n);
for (int i = 0; i < n; i++) {
std::cin >> a[i];
cc[i] = a[i];
}
std::sort(cc.begin(), cc.end());
cc.erase(std::unique(cc.begin(), cc.end()), cc.end());
int m = cc.size();
for (int i = 0; i < n; i++) {
cca[i] = std::lower_bound(cc.begin(), cc.end(), a[i]) - cc.begin();
}
long long ans = 0;
for (int i = 0; i < n; i++) {
for (int j = i + 1; j < n; j++) {
ans += a[i] * a[j];
}
}
if (h == 0) {
std::cout << ans << std::endl;
return 0;
}
auto get_cost = [&a, &cca, m](int l, int r)
{
static long long q = 0;
static long long sum = 0;
static int ll = 0, rr = 0;
static std::vector<int> exist(m);
if (ll == rr) ll = rr = l;
while (rr < r) {
if (rr - ll >= 1) sum += std::abs(a[rr] - a[rr - 1]) + (a[rr] ^ a[rr - 1]);
q -= sqr(exist[cca[rr]]);
exist[cca[rr]]++;
q += sqr(exist[cca[rr]]);
rr++;
}
while (l < ll) {
ll--;
if (rr - ll >= 2) sum += std::abs(a[ll] - a[ll + 1]) + (a[ll] ^ a[ll + 1]);
q -= sqr(exist[cca[ll]]);
exist[cca[ll]]++;
q += sqr(exist[cca[ll]]);
}
while (r < rr) {
rr--;
if (rr - ll >= 1) sum -= std::abs(a[rr] - a[rr - 1]) + (a[rr] ^ a[rr - 1]);
q -= sqr(exist[cca[rr]]);
exist[cca[rr]]--;
q += sqr(exist[cca[rr]]);
}
while (ll < l) {
if (rr - ll >= 2) sum -= std::abs(a[ll] - a[ll + 1]) + (a[ll] ^ a[ll + 1]);
q -= sqr(exist[cca[ll]]);
exist[cca[ll]]--;
q += sqr(exist[cca[ll]]);
ll++;
}
return q * sum;
};
std::vector<long long> f(n + 1, std::numeric_limits<long long>::max() / 2);
std::vector<long long> g(n + 1);
f[0] = 0;
for (int i = 0; i < k; i++) {
std::fill(g.begin(), g.end(), std::numeric_limits<long long>::max() / 2);
auto solve = [&f, &g, &get_cost](auto &&self, int l, int r, int pl, int pr) -> void
{
int mid = l + (r - l) / 2;
int p = l;
for (int i = pl; i < mid && i <= pr; i++) {
long long w = f[i] + get_cost(i, mid);
if (g[mid] > w) {
p = i;
g[mid] = w;
}
}
if (l < mid) self(self, l, mid - 1, pl, p);
if (mid < r) self(self, mid + 1, r, p, pr);
};
solve(solve, 0, n, 0, n);
std::swap(f, g);
}
std::cout << ans + f[n] << std::endl;
}