#dp#洛谷 5774 [JSOI2016]病毒感染
分析
此题肯定不是绿题,哪有这么恶心的dp
试想这样的情形:假设当 JYY 第一次抵达村庄 \(i\),未作救治并直接前往了另一个村庄。那么由于 \(i\) 村庄的人们求生心切,
一旦当 JYY 朝向靠近 \(i\) 村庄的方向前行时,\(i\) 村庄的村民就会以为 JYY 是来救他们了,而产生巨大的期望。
之后倘若 JYY 再次掉头朝着远离 \(i\) 村庄的方向行进,那么 \(i\) 村庄的村民就会因为巨大的失落而产生绝望的情绪。
所以JYY应该是一段一段治愈的,设\(dp[i]\)表示JYY治愈完前\(i\)个村庄的最少不幸人数
\(dp[i]=\min\{dp[j]+???(calc(j+1,i))+(s[n]-s[i])*(???)\}\),这样\(O(n^2)\)的dp明显还不够,需要预处理一些东西,
首先这一段应该是从\(j+1\)到\(i\)再到\(j+1\)再到\(i\),先考虑后面的天数就是\(4*(i-j-1)+1+1\),
也就是往返三遍再治愈当中所有村民总计4遍(治愈要加1),还要加上从\(j\)到\(j+1\)的天数
考虑中间\(calc\)的部分,\(calc(j,i)\)可以选择治愈\(j\)先(\(j+1\sim i\)都拖延1天)或者先治愈\(j+1\sim i\)再回来治愈\(j\),
那也就是
\[calc(j,i)=calc(j+1,i)+s[i]-s[j]+\min\{3*(i-j)*a[j],s[i]-s[j]\}
\]
正序枚举\(i\)倒序枚举\(j\)就可以做到\(O(n^2)\)
综上所述
\[dp[i]=\min\{dp[j]+calc(j+1,i)+(s[n]-s[i])*(4*(i-j-1)+2)\}
\]
代码
#include <cstdio>
#include <cctype>
#include <cstring>
#define rr register
using namespace std;
const int N=3011; typedef long long lll;
lll a[N],s[N],dp[N],f[N][N],n;
inline signed iut(){
rr int ans=0; rr char c=getchar();
while (!isdigit(c)) c=getchar();
while (isdigit(c)) ans=(ans<<3)+(ans<<1)+(c^48),c=getchar();
return ans;
}
inline lll calc(int l,int r){return s[r]-s[l-1];}
inline lll min(lll a,lll b){return a<b?a:b;}
signed main(){
n=iut(),memset(dp,42,sizeof(dp)),dp[0]=0;
for (rr int i=1;i<=n;++i)
s[i]=s[i-1]+(a[i]=iut());
for (rr int i=1;i<=n;++i){
f[i][i]=0;
for (rr int j=i-1;j;--j)
f[j][i]=f[j+1][i]+calc(j+1,i)+min(3*(i-j)*a[j],calc(j+1,i));
}
for (rr int i=1;i<=n;++i)
for (rr int j=0;j<i;++j)
dp[i]=min(dp[i],dp[j]+f[j+1][i]+((i-j-1)<<2|2)*calc(i+1,n));
return !printf("%lld",dp[n]);
}