区间DP。
我们先给人编号,从左到右编号1到n。
对于整段区间,必然有一个人是最后上场的,假设是编号为S的最后上场,
因为是栈维护的顺序,那么编号1至S-1这S-1个人,必然是第1--S-1个上场的(具体顺序不知),
而编号S+1至n这些人必然是第S--n-1个上场的(具体顺序不知)。
假设我们知道左边这些人上场的最优解与右边的人上场的最优解,
即可推导得出在编号S最后上场的情况下的最优解。
如何计算一段区间的最优解?可以设计区间DP,dp[i][j]表示编号i至j这群人上场的最优解。
假设现在要得到dp[st][en]这段区间最优解,
最优解为:min{ dp[st][s-1]+(en-st)*a[s]+dp[s+1][en]+left*(sum[en]-sum[s]) }
其中S是枚举最后哪个人上场,left表示st--S-1区间长度
上式中,dp[st][s-1]表示S左边编号st至s-1的人上场的最优解,(en-st)*a[s]表示S最后上场的代价,
dp[s+1][en]+left*(sum[en]-sum[s])表示S+1--en这一段最优解。
#include<cstdio> #include<cmath> #include<algorithm> #include<cstring> using namespace std; const int maxn=100+10; int a[maxn],sum[maxn]; int dp[maxn][maxn]; int T,n; void read() { scanf("%d",&n); for(int i=1;i<=n;i++) scanf("%d",&a[i]); } void work() { for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) dp[i][j]=0x7FFFFFFF; memset(sum,0,sizeof sum); for(int i=1;i<=n;i++) sum[i]=sum[i-1]+a[i]; for(int i=1;i<=n;i++) { for(int j=1;j<=n;j++) { int st=j,en=st+i-1; if(en>n) continue; if(i==1) {dp[st][en]=0;continue;} for(int s=st;s<=en;s++) { int left=s-st,right=en-s; if(!left) dp[st][en]=min(dp[st][en], dp[s+1][en]+a[s]*(i-1)); else if(!right) dp[st][en]=min(dp[st][en], dp[st][s-1]+a[s]*(i-1)); else dp[st][en]=min(dp[st][en], dp[st][s-1]+ dp[s+1][en]+ (i-1)*a[s]+ left*(sum[en]-sum[s])); } } } printf("%d ",dp[1][n]); } int main() { int c=1; scanf("%d",&T); while(T--) { read(); printf("Case #%d: ",c++); work(); } return 0; }