题目链接
Time Limit: 1000MS | Memory Limit: 10000K | |
Total Submissions: 3297 | Accepted: 1518 |
Description
Input
Output
Sample Input
5 1 1 3 3 2 4 3 2 3 1 4
Sample Output
153
题意:n个工作,编号为1到n,现在要把工作分成若干个连续但是不相交的区间。从第一个数所在的区间开始,依次完成每个区间的工作。假设某个区间包含的工作为(i到j),那么完成这个区间的工作要花的时间为(s+Ti+...Tj),s为启动时间一个常量,Ti为完成第i个工作要花的时间。设T为完成前i-1个工作所花的时间,那么完成这个区间的花费为(T+s+Ti+...Tj)*(Fi+...Fj)。求完成n个工作的最小花费。
(对于斜率优化dp,可以看看资料《1D/1D动态规划优化初步》)
题解:容易想到从前往后推的DP,dp[i][j]表示完成前i个工作花费时间j的最小花费,但是这样无论空间和时间都不能接受。这题有个很巧妙的地方在于,对于从后往前推的DP来说,我们只用开一维状态就行了。
用dp[i]表示完成i到n的工作的最小花费,转移如下:
dp[i]=min(dp[j]+(s+Ti+..T(j-1))*(Fi+...Fn)),i
我们用sumT(i)表示Ti+T(i+1)+...T(n)。
用sumF(i)表示Fi+F(i+1)+....F(n)。
转移方程还可以写成:
dp[i]=min(dp[j]+(s+sumT(i)-sumT(j))*sumF(i)),i
dp[i]=min(dp[j]-sumT(j)*sumF(i))+(s+sumT(i))*sumF(i)
要求min(dp[j]-sumT(j)*sumF(i)),可以发现这是经典的斜率优化模型。
设P=(dp[j]-sumT(j)*sumF(i)),i
设:y(j)=dp[j],x(j)=sumT(j),k(i)=sumF(i)
那么:y(j)=k(i)*x(j)+p。
把x看成横坐标,y看成纵坐标,那么每一个决策j,看以看成二维平面上的一个点。
对求dp[i]的时候,k(i)是已知的,问题就转化成斜率已知的一条直线,要让它从一些点(决策j)中选择穿过一个点,使得直线的纵截距P最小。这个时候有一个重要的性质:所有最优决策点都在平面点集的凸包上。
对于这题来说,随着i的增加,K(i)是递增的,X(i)也是递增的。由于斜率单调,那么最优决策点一定在凸包上单调移动。我们可以维护一个单调队列。队列里面存的是当前加入的点中在凸包上的并且有可能为最优策略的点。队列满足两个性质:
1,队列里点的横坐标单调递增。
2,队列里相邻两点的斜率单调递增。
队列的具体维护:
1,加入一个点。当dp[i]更新完以后,将点(X(i),Y(i))加入队列。判断将点加入以后,是否满足上面的第2个性质,不满足则队尾出队。直至队列为空或者满足第2个性质。
2,删除一个点。当更新dp[i]的时候,判断如果队首元素比队首元素的下一个元素更优或者队列只有一个元素,那么队首元素就是最优策略,否则队首元素出队。
代码如下:
#include
#include
#include
#include
#include
#define nn 11000
typedef __int64 LL;
using namespace std;
int n,s;
int T[nn],F[nn];
LL st[nn],sf[nn];
LL dp[nn];
struct node
{
LL x,y;
double xl;
node(){}
node(LL xx,LL yy,double ll)
{
xl=ll;
x=xx,y=yy;
}
}que[nn],tem;
int top,pop;
void add(LL x,LL y)
{
while(toptem.xl)
break;
else
pop--;
}
if(top==pop)
{
que[pop++]=node(x,y,0);
return ;
}
tem=que[pop-1];
que[pop++]=node(x,y,double(y-tem.y)/(double(x-tem.x)));
}
LL chu(LL x)
{
while(top+1=1;i--)
{
st[i]=st[i+1]+T[i];
sf[i]=sf[i+1]+F[i];
}
top=pop=0;
add(st[n+1],dp[n+1]);
for(i=n;i>=1;i--)
{
dp[i]=chu(sf[i])+(s+st[i])*sf[i];
add(st[i],dp[i]);
}
printf("%I64d\n",dp[1]);
}
return 0;
}
这题还有另一种做法:
dp[i]=min(dp[j]+(s+sumT(i)-sumT(j))*sumF(i)),i
我们令w(i,j)=(s+sumT(i)-sumT(j))*sumF(i)
容易证明:w(i,j)+w(i+1,j+1)<=w(i+1,j)+w(i,j+1)
所以可知决策是满足单调性的。
由于决策单调,我们可以在O(nlgn)的时间内解决问题。
具体解决方法可参加资料《1D/1D动态规划优化初步》。
代码如下:
#include
#include
#include
#include
#include
#define nn 11000
typedef __int64 LL;
using namespace std;
int n,s;
int T[nn],F[nn];
LL st[nn],sf[nn];
LL dp[nn];
struct node
{
int val,l,r;
node(){}
node(int x,int y,int z)
{
val=x,l=y,r=z;
}
}sta[nn],tem;
bool check(int id,int pre,int now)
{
if(dp[pre]+(s+st[id]-st[pre])*sf[id]>dp[now]+(s+st[id]-st[now])*sf[id])
return true;
return false;
}
int main()
{
int i,j;
while(scanf("%d%d",&n,&s)!=EOF)
{
for(i=1;i<=n;i++)
{
scanf("%d%d",&T[i],&F[i]);
}
st[n+1]=sf[n+1]=0;
dp[n+1]=0;
for(i=n;i>=1;i--)
{
st[i]=st[i+1]+T[i];
sf[i]=sf[i+1]+F[i];
}
int top=0,pop=0;
sta[pop++]=node(n+1,1,n);
for(i=n;i>=1;i--)
{
for(;toptop;pop--)
{
if(check(sta[pop-1].r,sta[pop-1].val,i))
{
tem.r=sta[pop-1].r;
continue;
}
else
{
if(!check(sta[pop-1].l,sta[pop-1].val,i))
break;
int l=sta[pop-1].l,r=sta[pop-1].r;
int mid;
while(l