【BZOJ4856】【JSOI2016】病毒感染

【题目链接】

  • 点击打开链接

【思路要点】

  • 由于起始点是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
using namespace std;
const int MAXN = 3005;
const long long INF = 4e18;
template  void chkmax(T &x, T y) {x = max(x, y); }
template  void chkmin(T &x, T y) {x = min(x, y); } 
template  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  void write(T x) {
	if (x < 0) x = -x, putchar('-');
	if (x > 9) write(x / 10);
	putchar(x % 10 + '0');
}
template  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;
}

你可能感兴趣的:(【OJ】BZOJ,【类型】做题记录,【算法】动态规划)