UOJ #1005. 【UR #32】王之钦定 题解
Description
跳蚤国计算机协会 UOI 主席 “王中王” 认为 UOI 决赛不具有观赏性。
比如蟋蟀国的比赛,选手都需要在初赛快速 AK 才能晋级决赛,但 UOI 决赛只需要通过不到一半的题目就可以获得三十二强。
但是经过 UOI 系列委员会的商讨,比赛难度并没有被下调,因此王中王决定黑幕一些比赛结果。
具体来说,UOI 决赛总 \(n\) 天,有 \(m\) 名选手参加。现在王中王通过第六感得知,如果不加干预第 \(i\) 天的比赛,那么第 \(a_i\) 位选手将会胜出,但王中王每天可以将当天题目提前透露给任意一名选手。如果王中王透露了第 \(i\) 天的题目,获得题目的选手将会成为那天比赛的胜利者,但他认为这样做比赛的合理性会降低 \(w_i\)。
不妨假设黑幕后每天的胜者依次为 \(b_1, b_2, \dots, b_n\)。王中王相信观众更喜欢看到“绝对强者”之间的对决,因此他认为一段赛程 \([l, r]\) 是“好看”的,当且仅当在该段赛程中,成为过胜者的选手数不超过二,即 \(|\{b_l, b_{l+1}, \dots, b_r\}| \leq 2\)。
而每一段“好看”的赛程,王中王认为都会让比赛的合理性增加 \(1\)。
而由于 UOI 决赛随时面临着缺投倒闭的风险,因此王中王想知道对于所有 \(k = 1, 2, \dots, n\),如果 UOI 决赛只举办前 \(k\) 天的比赛,比赛的合理性最高能是多少?
\(1 \leq n \leq 1500, 1 \leq a_i \leq m\leq 50, 0 \leq w_i \leq 10^6\)。
Solution
定义一个区间 \([l,r]\) 是好的,当且仅当 \([l,r]\) 中颜色数量不超过 \(2\),把所有极长好区间拿出来,设其为 \([l_1,r_1],[l_2,r_2],\ldots,[l_k,r_k]\)。
不妨设 \(l_1<l_2<\ldots<l_k\) 且 \(r_1<r_2<\ldots<r_k\),则 \([l_i,r_i]\) 和 \([l_{i+1},r_{i+1}]\) 一定有交,且交是一段极长颜色连续段。同时 \([l_i,r_i]\) 与其相邻的两个区间的两个交一定没有交集,否则 \([l_i,r_i]\) 颜色就全相同了,可以和相邻的区间合并。
钦定 \([l_i,r_i]\) 中出现的颜色为 \(1\) 和 \(2\),定义 \([l_i,r_i]\) 的中间部分是整个区间去掉与相邻区间的交后剩下的区间,容易发现中间部分的两端点一定没有被操作,否则操作成与相邻区间的交的颜色一定更优。考虑分讨 \([l_i,r_i]\) 与相邻区间的交集的颜色情形:
- \(111\,222\):此时中间部分大小为 \(0\),前半部分是与 \(i-1\) 的交,后半部分是与 \(i+1\) 的交。
- \(111\,21\,222\):中间部分非空,且与 \(i-1\) 的交的颜色和与 \(i+1\) 的交的颜色不同。
- \(111\,212\,111\):中间部分非空,且与 \(i-1\) 的交的颜色和与 \(i+1\) 的交的颜色相同。
由于对于一段区间,我们只关心这个区间与下一个区间交的部分的状态,所以可以对这个设计状态。
设 \(f_{i,j,c}\) 表示目前考虑了一个极长好区间,右端点为 \(j\),钦定与下一段好区间的交是 \([i,j]\),且颜色为 \(c\) 时,\([1,j]\) 的最优贡献。考虑按照左端点从小到大进行转移,
对于第一种情况可以直接枚举下一段交区间的长度和颜色来转移。
对于第二/三种情况,需要加上中间部分的贡献,直接枚举的话转移到后面的交区间之前都要 \(O(n^3m)\) 。考虑加入辅助状态,设 \(g_{x,y,c_1,c_2}\) 表示对于所有左端点为 \(x\),右端点小于等于 \(y\) 的交区间,钦定后面这段区间的两种颜色是 \(c_1,c_2\) 时,\([1,y]\) 的最优贡献。这个辅助状态可以在从小到大枚举左端点的时候一起转移,时间复杂度是 \(O(n^2m^2)\)。
得到 \(g\) 的值后,枚举第二段交区间的长度和颜色以及区间内出现的另一个颜色后转移就和第一种情况就差不多了,但是会比第一种情况多一个 \(m\)。
设钦定的第二段交区间是 \([i',j']\),根据上面的性质,中间部分的两端点都不会被操作,所以区间内的另一个颜色一定就是 \(i'-1\) 的初始颜色 \(a_{i'-1}\),复杂度也就变得和第一种情况一样了。
推一下会发现直接转移是 \(O(n^3m)\) 的,注意到转移是最大化 \(k_ix+b_i\) 的形式,同时 \(x\) 单调递增,所以维护关于 \((k_i,b_i)\) 的上凸壳,再在凸壳上维护一个指针表示当前最优的 \((k_i,b_i)\) 即可。
时间复杂度:\(O(n^2m^2)\),瓶颈是 \(g\) 的转移,常数很小,可以过。
具体实现细节见代码。
Code
#include <bits/stdc++.h>
// #define int int64_t
template<class T>
struct Vector {
T x, y;
Vector() {}
Vector(T _x, T _y) : x(_x), y(_y) {}
friend bool operator ==(Vector a, Vector b) { return a.x == b.x && a.y == b.y; }
friend bool operator !=(Vector a, Vector b) { return a.x != b.x || a.y != b.y; }
friend Vector operator -(Vector a) { return {-a.x, -a.y}; }
friend Vector operator +(Vector a, Vector b) { return {a.x + b.x, a.y + b.y}; }
friend Vector operator -(Vector a, Vector b) { return {a.x - b.x, a.y - b.y}; }
friend T operator *(Vector a, Vector b) { return a.x * b.y - a.y * b.x; }
template<class _T> friend Vector operator *(Vector a, _T b) { return {a.x * b, a.y * b}; }
template<class _T> friend Vector operator *(_T a, Vector b) { return {a * b.x, a * b.y}; }
template<class _T> friend Vector operator /(Vector a, _T b) { return {a.x * 1.0 / b, a.y * 1.0 / b}; }
friend Vector operator +=(Vector &a, Vector b) { return a = {a.x + b.x, a.y + b.y}; }
friend Vector operator -=(Vector &a, Vector b) { return a = {a.x - b.x, a.y - b.y}; }
template<class _T> friend Vector operator *=(Vector &a, _T b) { return a = {a.x * b, a.y * b}; }
template<class _T> friend Vector operator /=(Vector &a, _T b) { return a = {a.x * 1.0 / b, a.y * 1.0 / b}; }
friend bool operator <(Vector a, Vector b) { return a.x < b.x || a.x == b.x && a.y < b.y; }
};
using i64 = int64_t;
using Vec = Vector<i64>;
const int kMaxN = 1.5e3 + 5, kMaxK = 55;
int n, m;
int a[kMaxN], w[kMaxN], sum[kMaxN], pre[kMaxK][kMaxN], f[kMaxN][kMaxN][kMaxK], g[kMaxN][kMaxK][kMaxK];
inline void chkmax(int &x, int y) { x = (x > y ? x : y); }
inline void chkmin(int &x, int y) { x = (x < y ? x : y); }
int getval(int c1, int c2, int l, int r) {
if (c1 == c2) c2 = 0;
return sum[r] - sum[l - 1] - (pre[c1][r] - pre[c1][l - 1]) - (pre[c2][r] - pre[c2][l - 1]);
}
int calc(Vec p, int x) { return x * p.x + p.y; }
void dickdreamer() {
std::cin >> n >> m;
for (int i = 1; i <= n; ++i) std::cin >> a[i];
for (int i = 1; i <= n; ++i) {
std::cin >> w[i];
sum[i] = sum[i - 1] + w[i];
for (int j = 1; j <= m; ++j)
pre[j][i] = pre[j][i - 1] + w[i] * (j == a[i]);
}
memset(f, 0xcf, sizeof(f));
memset(g, 0xcf, sizeof(g));
for (int i = 1; i <= n; ++i) {
for (int c1 = 1; c1 <= m; ++c1) {
int mi = 1e9;
for (int c2 = 1; c2 <= m; ++c2) chkmin(mi, getval(c1, c2, 1, i));
for (int j = i + 1; j <= n; ++j) {
chkmax(f[i + 1][j][c1], 1ll * j * (j + 1) / 2 - mi - getval(c1, c1, i + 1, j));
}
}
}
for (int i = 2; i < n; ++i) {
for (int j = 2; j <= i; ++j) {
for (int c1 = 1; c1 <= m; ++c1) {
for (int c2 = c1; c2 <= m; ++c2) {
chkmax(g[j][c1][c2], std::max(f[j][i - 1][c1], f[j][i - 1][c2]));
g[j][c1][c2] += (i - j + 1) - (a[i] != c1 && a[i] != c2) * w[i];
}
}
}
{
static int mx[kMaxN];
for (int j = 2; j <= i; ++j) {
mx[j] = -1e9;
for (int c = 1; c <= m; ++c) chkmax(mx[j], f[j][i][c]);
}
for (int c = 1; c <= m; ++c) {
std::vector<Vec> stk;
for (int k = i; k >= 2; --k) {
Vec p = {i - k + 1, mx[k]};
for (; stk.size() >= 2 && (p - stk.back()) * (stk.back() - stk[stk.size() - 2]) <= 0; stk.pop_back()) {}
stk.emplace_back(p);
}
for (int j = i + 1, pos = 0; j <= n; ++j) {
int now = (j - i) * (j - i + 1) / 2 - getval(c, c, i + 1, j);
// for (int k = 2; k <= i; ++k) chkmax(f[i + 1][j][c], mx[k] + now + (j - i) * (i - k + 1));
for (; pos + 1 < stk.size() && calc(stk[pos + 1], j - i) > calc(stk[pos], j - i); ++pos) {}
chkmax(f[i + 1][j][c], now + calc(stk[pos], j - i));
}
}
}
for (int c = 1; c <= m; ++c) {
int c1 = std::min(c, a[i]), c2 = std::max(c, a[i]);
std::vector<Vec> stk;
for (int k = i; k >= 2; --k) {
Vec p = {i - k + 1, g[k][c1][c2]};
for (; stk.size() >= 2 && (p - stk.back()) * (stk.back() - stk[stk.size() - 2]) <= 0; stk.pop_back()) {}
stk.emplace_back(p);
}
for (int j = i + 1, pos = 0; j <= n; ++j) {
int now = (j - i) * (j - i + 1) / 2 - getval(c, c, i + 1, j);
// for (int k = 2; k <= i; ++k) chkmax(f[i + 1][j][c], mx[k] + now + (j - i) * (i - k + 1));
for (; pos + 1 < stk.size() && calc(stk[pos + 1], j - i) > calc(stk[pos], j - i); ++pos) {}
chkmax(f[i + 1][j][c], now + calc(stk[pos], j - i));
}
}
}
for (int i = 1; i <= n; ++i) {
if (i == 1) {
std::cout << 1 << ' ';
} else {
int ans = -1e9;
for (int j = 2; j <= i; ++j)
for (int c = 1; c <= m; ++c)
chkmax(ans, f[j][i][c]);
std::cout << ans << ' ';
}
}
}
int32_t main() {
#ifdef ORZXKR
freopen("in.txt", "r", stdin);
freopen("out.txt", "w", stdout);
#endif
std::ios::sync_with_stdio(0), std::cin.tie(0), std::cout.tie(0);
int T = 1;
// std::cin >> T;
while (T--) dickdreamer();
// std::cerr << 1.0 * clock() / CLOCKS_PER_SEC << "s\n";
return 0;
}