1. 程式人生 > 實用技巧 >#dp#洛谷 5774 [JSOI2016]病毒感染

#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]);
}