Solution - HDU 6157 The Karting
帅哥 Vjudge:Link。
一开始真的被抽象到了。首先因为权值可能是负的,肯定不能贪心;也没有其它好的想法,考虑 dp。
但是怎么设计状态呢?如果单纯记录路径(比如记录终点啥的),那么判不了重。我们考虑,路径怎么走的并不重要,我们只关心在哪些点处改变了方向(拐点)。分析样例,走的 \(1 \to 2 \to 3 \to 2 \to 3 \to 4 \to 3 \to 2 \to 1\):
1 2 3 4
->->x
x<-
->->x
x<-<-<-
只关心拐点 \(1, 3, 2, 4\),可以发现,对于往左再往右中间的点(如 \(1, 2\)),贡献是负的两端前缀路径长度加上改变方向所用费用;对于往右再往左中间的点(如 \(3, 4\)),贡献是正的两端前缀路径长度加上改变方向所用费用。通过这样对拐点费用的加减,就可以正确地维护每条路径的贡献。所以只需要依次计算关键点,加上贡献,就可以了,自然不会重复。这也就是费用提前计算。
设 \(f_{i, j, k}\) 表示前 \(i\) 个点,选出 \(j\) 个关键点,往左再往右中间的拐点数量减去往右再往左中间拐点的数量为 \(k\) 时,最大的贡献。因为我们一定先走的是“往左再往右”,一直有 \(k \geq 0\)。注意关键点并非一定是拐点,可以在中间!!!
转移分为不选第 \(i\) 个点为关键点;选第 \(i\) 个点为关键点,不是拐点(上面强调的);选第 \(i\) 个点为关键点,是往左再往右 / 往右再往左拐点的情况。方程懒得写,比较 naive。
namespace liuzimingc {
#define endl '\n'
const int N = 105;
int n, m, d[N], D, f[N][N][N];
int main() {
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
while (cin >> n >> m) {
cin >> D;
for (int i = 2; i <= n; i++) cin >> d[i], d[i] += d[i - 1];
memset(f, -0x3f, sizeof(f));
f[0][0][0] = 0;
for (int i = 1; i <= n; i++) {
f[i][0][0] = 0;
for (int j = 1; j <= i; j++)
for (int k = 0; k <= j; k++) {
f[i][j][k] = max(f[i][j][k], f[i - 1][j][k]); // 不选
f[i][j][k] = max(f[i][j][k], f[i - 1][j - 1][k]); // 选,不是拐点
f[i][j][k] = max(f[i][j][k], f[i - 1][j - 1][k + 1] + D + 2 * d[i]); // 往右再往左
if (k)
f[i][j][k] = max(f[i][j][k], f[i - 1][j - 1][k - 1] + D - 2 * d[i]); // 往左再往右
}
}
cout << f[n][m][0] << endl;
}
return 0;
}
} // namespace liuzimingc
upd:更抽象的一点。
Posted by liuzimingc