bzoj 4856 [Jsoi2016]病毒感染 区间dp

题面

题目传送门

解法

  • 首先显然可以发现,某一次回头一定会把之前没有被救过的村庄全都救一遍,那么最后的路径一定是不断向前走然后回头再往前走,直到所有村庄全都被救为止。
  • 那么我们可以设 f [ i ] f[i] f[i]表示编号为 1 − i 1-i 1i的村庄全部被救最少的死亡人数。
  • 对于转移,可以枚举这一次回头后到达的最后一个村庄,即这一轮最早一开始经过但没有救的村庄的编号 j j j,那么便有如下转移:
  • f [ i ] = m i n ( f [ j ] + g [ j ] [ i ] + ( s [ n ] − s [ i ] ) ∗ [ ( i − j ) ∗ 3 + ( i − j + 1 ) + 1 ] ) f[i]=min(f[j]+g[j][i]+(s[n]-s[i])*[(i-j)*3+(i-j+1)+1]) f[i]=min(f[j]+g[j][i]+(s[n]s[i])[(ij)3+(ij+1)+1]),其中 g [ j ] [ i ] g[j][i] g[j][i]表示在从 j j j i i i再到 j j j最后返回 i i i的过程中最少的死亡人数,且 j j j一开始被经过时并不会救。
  • 那么考虑如何求解 g [ i ] [ j ] g[i][j] g[i][j]。我们可以设 h [ i ] [ j ] h[i][j] h[i][j]表示在从抵达 i i i这个村庄到救完 [ i , j ] [i,j] [i,j]中的所有村庄并回到 j j j这段时间中最少的死亡人数。
  • 考虑 i i i这个点是否在经过时就被救,那么有转移 h [ i ] [ j ] = h [ i + 1 ] [ j ] + m i n ( s [ j ] − s [ i ] + a [ i ] ∗ ( j − i ) ∗ 3 , ( s [ j ] − s [ i ] ) ∗ 2 ) h[i][j]=h[i+1][j]+min(s[j]-s[i]+a[i]*(j-i)*3,(s[j]-s[i])*2) h[i][j]=h[i+1][j]+min(s[j]s[i]+a[i](ji)3,(s[j]s[i])2)
  • 求解完 h [ i ] [ j ] h[i][j] h[i][j]后,那可以自然地求出 g [ i ] [ j ] = h [ i + 1 ] [ j ] + s [ j ] − s [ i ] + a [ i ] ∗ 3 ∗ ( j − i ) g[i][j]=h[i+1][j]+s[j]-s[i]+a[i]*3*(j-i) g[i][j]=h[i+1][j]+s[j]s[i]+a[i]3(ji)
  • 时间复杂度: O ( n 2 ) O(n^2) O(n2)

代码

#include <bits/stdc++.h>
#define ll long long
using namespace std;
template <typename T> void chkmax(T &x, T y) {x = x > y ? x : y;}
template <typename T> void chkmin(T &x, T y) {x = x < y ? x : y;}
template <typename T> void read(T &x) {
	x = 0; int f = 1; char c = getchar();
	while (!isdigit(c)) {if (c == '-') f = -1; c = getchar();}
	while (isdigit(c)) x = x * 10 + c - '0', c = getchar(); x *= f;
}
const int N = 3010;
ll a[N], f[N], s[N], g[N][N], h[N][N];
int main() {
	int n; read(n);
	for (int i = 1; i <= n; i++) read(a[i]), s[i] = s[i - 1] + a[i];
	for (int l = 1; l <= n; l++)
		for (int i = 1; i <= n - l + 1; i++) {
			int j = i + l - 1;
			h[i][j] = h[i + 1][j] + min(s[j] - s[i] + a[i] * 3 * (j - i), (s[j] - s[i]) * 2);
		}
	for (int l = 1; l <= n; l++)
		for (int i = 1; i <= n - l + 1; i++) {
			int j = i + l - 1;
			g[i][j] = h[i + 1][j] + s[j] - s[i] + a[i] * 3 * (j - i);
		}
	memset(f, 0x3f, sizeof(f)); f[0] = 0;
	for (int i = 1; i <= n; i++)
		for (int j = 1; j <= i; j++)
			chkmin(f[i], f[j - 1] + g[j][i] + (s[n] - s[i]) * ((i - j) * 3 + i - j + 2));
	cout << f[n] << '\n';
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值