传送门:http://acm.hdu.edu.cn/showproblem.php?pid=4283
You Are the One
Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others)Total Submission(s): 4176 Accepted Submission(s): 1943
The next n line are n integer D1-Dn means the value of diaosi of boys (0 <= Di <= 100)
2 5 1 2 3 4 5 5 5 4 3 2 2
Case #1: 20 Case #2: 24
有一个队列要上舞台,有一个栈可以进行对队列顺序的调度,在队列中每个人有一个愤怒比率,该人的愤怒值等于该人的愤怒比率*(k-1)(k代表该人第k个上场的)。问怎样调度能够使得 总 的愤怒值最小。
题解:
该题一开始就是想得用贪心做,题目也没说每个人进栈出栈的次数,所以感觉没问题,可是看了题解后竟然是区间Dp,理解了也是很长时间。
说一下我对这个区间DP解法的理解:对于队列 (L (i , j) ,(k R)),对于任意区间[L,R]进行调度,假设这个区间是在队列的最前端,那么对L可以放到[L,r]的任何一个位置,假设L是第K个出场的,那么区间[i,j]的人要先与L入场,区间[k,R]的人要后于L入场,此时L的愤怒值为d[L]*(k-1);区间[i,j]和区间[k,R]的操作也是一样的(注意对每个区间操作时先都把它当做是队列的最前段的区间)。那么怎么计算区间的愤怒值呢?对于先于L入场的区间[i,j]而言,因为在计算区间[i,j]的时候本身就是把区间[i,j]当做队列的最前段来计算的,而在计算区间[L,R],区间[i,j]仍然在最前段没有改变入场的位置所以其愤怒值就是它本身。(**)而对于区间[k,R],在计算它的时候也是先把它当做队列的最前段来计算的,但是现在在队列[L,R]中变成了在k个人的后面了,也就是其愤怒值增加了,那么增加了多少呢?记sum[i]表示前i个人的愤怒比率和,那么区间[k,R]的愤怒值 增加了 k*(sum[R]-sum[k-1]).至此可以写一下状态转移方程的通式了:
dp[i][j]=min(dp[i+1][i+k-1]+dp[i+k][j]+d[i]*(k-1)+k*(sum[j]-sum[i+k-1]));
*笔者不才,写的逻辑有点混乱,但是多读几遍就能知道它的整个过程了,再结合代码看看。
#include <iostream>
#include <cstring>
#include <cstdio>
#include <cstdlib>
#include <algorithm>
#include <cmath>
#define INF 0x3f3f3f3f;
using namespace std;
int d[111],dp[111][111],sum[111];
int main()
{
int T,w=0;
cin>>T;
while(T--)
{
int n;
scanf("%d",&n);
memset(dp,0,sizeof(dp));
sum[0]=0;
for(int i=1;i<=n;i++)
{
scanf("%d",&d[i]);
sum[i]=sum[i-1]+d[i];
}
for(int len=1;len<n;len++)
{
for(int i=1;i+len<=n;i++)
{
int j=i+len;
dp[i][j]=INF;///只设置从i->j是INF,因为可能出现区间(l,r)使得r<l的情况(当k=1||k==len+1时),此时dp==0
for(int k=1;k<=len+1;k++)
{
dp[i][j]=min(dp[i][j],dp[i+1][i+k-1]+dp[i+k][j]+d[i]*(k-1)+k*(sum[j]-sum[i+k-1]));
}
}
}
printf("Case #%d: %d\n",++w,dp[1][n]);
}
return 0;
}
&spm=1001.2101.3001.5002&articleId=76169182&d=1&t=3&u=8435907850cd45ce89a220eb3cebbe1a)
246

被折叠的 条评论
为什么被折叠?



