Luogu

分析

首先那个规定的意思是如果往左走就要把左边所有没治愈的村庄治愈。

可以发现整个过程可以划分成若干段,每段以任意顺序治愈后回到该段的起点,再治愈下一段。

考虑 DP。设 $dp_i$ 表示治愈前 $i$ 个村庄的最小死亡人数。记 $s$ 为 $a$ 的前缀和,$w_{i,j}$ 为从 $i$ 出发治愈 $[i,j]$ 中所有村庄再回到 $i$ 的最小死亡人数,容易得到转移
$$
dp_i=\min_{j<i}\left\{f_j+[j\neq 0](s_n-s_j)+w_{j+1,i}+[i\neq n](i-j-1)(s_n-s_i)\right\}
$$
边界为 $dp_0=0$。

考虑 $w_{i,j}$ 如何计算。从 $w_{i+1,j}$ 转移,枚举 $i$ 一开始治不治愈可以得到
$$
w_{i,j}=w_{i+1,j}+\min\begin{cases}3(j-i)a_i+(s_n-s_i)+2(s_n-s_j)\\2(s_n-s_i)+(s_n-s_j)\end{cases}
$$
边界为 $w_{i,i}=s_n-s_i$。

代码

// ===================================
//   author: M_sea
//   website: http://m-sea-blog.com/
// ===================================
#include <bits/stdc++.h>
#define re register
using namespace std;
typedef long long ll;

inline int read() {
    int X=0,w=1; char c=getchar();
    while (c<'0'||c>'9') { if (c=='-') w=-1; c=getchar(); }
    while (c>='0'&&c<='9') X=X*10+c-'0',c=getchar();
    return X*w;
}

const int N=3000+10;

int n; ll a[N],s[N];
ll w[N][N],dp[N];

int main() {
    n=read();
    for (re int i=1;i<=n;++i) a[i]=read();
    for (re int i=1;i<=n;++i) s[i]=s[i-1]+a[i];
    for (re int j=1;j<=n;++j) {
        w[j][j]=s[n]-s[j];
        for (re int i=j-1;i;--i)
            w[i][j]=w[i+1][j]+min(2*(s[n]-s[i])+s[n]-s[j],
                                  3*a[i]*(j-i)+2*(s[n]-s[j])+s[n]-s[i]);
    }
    memset(dp,0x3f,sizeof(dp)),dp[0]=0;
    for (re int i=1;i<=n;++i)
        for (re int j=0;j<i;++j)
            dp[i]=min(dp[i],dp[j]+(j!=0)*(s[n]-s[j])
                           +w[j+1][i]+(i-j-1)*(s[n]-s[i]));
    printf("%lld\n",dp[n]);
    return 0;
}
最后修改:2021 年 03 月 24 日 03 : 05 PM