【题目链接】
【思路要点】
- 由于起始点是1号节点,如果在某个时刻,我们向左走了一个单位,那么我们就必须将左侧的村庄全部治愈。
- 因此,如果我们能计算出\(last_{i,j}\)表示从村庄\(i\)出发,治愈\([i,j]\)内所有村庄并走到村庄\(j+1\)所付出的最小代价(注意这里的代价应当计算\([j+1,N]\)内的村庄在该过程中的死亡)那么就可以用一个简单的DP解决本题。
- 现在问题在于如何计算出\(last_{i,j}\)。
- 考虑枚举\(j\),这样\([j+1,N]\)内的村庄村民死亡的速度就确定了。
- 令\(suf_i\)表示\([i,N]\)内村庄的权值和。
- 若已知\(last_{i+1,j}\),\(last_{i,j}\)可能有两种转移方式:
- 1、在村庄\(i\)处先治愈村庄\(i\),总代价为\(2 * suf_{i + 1} + last_{i + 1,j}\)。
- 2、在治愈\([i+1,j]\)内所有村庄后治愈村庄\(i\),总代价为\(first_{i,j} + suf_{j + 1} * (j - i + 1)\)。
- 其中\(first_{i,j}\)表示从村庄\(i\)出发,治愈\([i,j]\)内所有村庄并走到村庄\(i\)所付出的最小代价。
- \(first_{i,j}\)可以用类似的转移求出,在此不再赘述。
- 时间复杂度\(O(N^2)\)。
【代码】
#include<bits/stdc++.h> using namespace std; const int MAXN = 3005; const long long INF = 4e18; template <typename T> void chkmax(T &x, T y) {x = max(x, y); } template <typename T> void chkmin(T &x, T y) {x = min(x, y); } template <typename T> void read(T &x) { x = 0; int f = 1; char c = getchar(); for (; !isdigit(c); c = getchar()) if (c == '-') f = -f; for (; isdigit(c); c = getchar()) x = x * 10 + c - '0'; x *= f; } template <typename T> void write(T x) { if (x < 0) x = -x, putchar('-'); if (x > 9) write(x / 10); putchar(x % 10 + '0'); } template <typename T> void writeln(T x) { write(x); puts(""); } int n; long long dp[MAXN], suf[MAXN], val[MAXN]; long long first[MAXN][MAXN], last[MAXN][MAXN]; int main() { read(n); for (int i = 1; i <= n; i++) read(val[i]); for (int i = n; i >= 1; i--) suf[i] = suf[i + 1] + val[i]; for (int i = n; i >= 1; i--) { first[i][i] = suf[i + 1]; last[i][i] = 2 * suf[i + 1]; for (int j = i - 1; j >= 1; j--) { first[j][i] = min(2 * suf[j + 1] + first[j + 1][i] + suf[i + 1], suf[j + 1] + first[j + 1][i] + 2 * suf[i + 1] + val[j] * (i - j) * 3); last[j][i] = min(2 * suf[j + 1] + last[j + 1][i], first[j][i] + suf[i + 1] * (i - j + 1)); } } for (int i = n; i >= 1; i--) { dp[i] = INF; for (int j = i; j <= n; j++) chkmin(dp[i], dp[j + 1] + last[i][j]); } writeln(dp[1]); return 0; }