初见安~这里是传送门:洛谷P2365
题目描述
N个任务排成一个序列在一台机器上等待完成(顺序不得改变),这N个任务被分成若干批,每批包含相邻的若干任务。从时刻0开始,这些任务被分批加工,第i个任务单独完成所需的时间是Ti。在每批任务开始前,机器需要启动时间S,而完成这批任务所需的时间是各个任务需要时间的总和(同一批任务将在同一时刻完成)。每个任务的费用是它的完成时刻乘以一个费用系数Fi。请确定一个分组方案,使得总费用最小。
例如:S=1;T={1,3,4,2,1};F={3,2,3,3,4}。如果分组方案是{1,2}、{3}、{4,5},则完成时间分别为{5,5,10,14,14},费用C={15,10,30,42,56},总费用就是153。
输入输出格式
输入格式:
第一行是N(1<=N<=5000)。
第二行是S(0<=S<=50)。
下面N行每行有一对数,分别为Ti和Fi,均为不大于100的正整数,表示第i个任务单独完成所需的时间是Ti及其费用系数Fi。
输出格式:
一个数,最小的总费用。
输入输出样例
输入样例:
5 1 1 3 3 2 4 3 2 3 1 4
输出样例:
153
题解
时间,费用,求最小——很像背包对不对!差不多了。往DP的方向想就对了。
这道题如果我们用贪心的算法的话,会有很多细节要处理,而且代码量相对而言也有点长【关键是还全错】,所以我们就不能考虑了。(我一开始就用的接近于贪心的算法然后样例输出153我输出了154……)
dp 的话代码量其实很短——我们可以看到:时间实在累计的,而每一批的任务所用的费用是所有的系数乘这一批完成时的时间点,都是需要累加的量,所以我们存的时候就累加存:
for(int i=1;i<=n;i++)
{
scanf("%d%d",&t[i],&f[i]);
t[i]+=t[i-1];f[i]+=f[i-1];
}
现在来考虑核心代码:如果我们去考虑要怎么分批的话,那就麻烦了——不说你也知道。这就是dp的巧妙之处了——
我们假设,算任务1~i(i<=n)的最小费用时,如果从第j(1<=j<i)个任务开始就分一批,那么之前存的所有数据似乎都要重新来算一遍——因为一直到任务i,时间为t[ i ],如果只到任务j,那么j~i的费用就不需要那么多了,而且还要加上开机时间。所以我们对前面的数据不能更改,而是适当利用。为了方便,我们就每次都重新算一次就好了。
dp时的两种方案:
1.不变
2.从任务j开始分批,则:
也就是说:本着dp[ i ]值的改动不影响dp[ j ]的原则,分批的话在这一步操作中是分为了三部分:任务1~j,值就是dp[ j ];任务j~i,就是完成任务i的时间点乘以这一堆任务的费用系数和(乘法分配律);任务i~n的值会因为j点强行分批而导致不得不多一个开机时间,所以要加上(是加上不是重新算。在dp[ j ]中后面的原情况已经存在了,只需要做一点改动)。也就是以上的三部分了。
*注意:
1.后值不影响前值。
2.点j的意思是:添加一个分割点,j以后的自动为一批。当然这里的一批并不一定是说最后分完了就只有3批,在取用dp[ j ]的时候其实就已经可以看出分了很多批了——因为算dp[ j ]的时候同样是这么分出来的。同理。
大致就是如此啦——我们现在来看看代码:)
#include<bits/stdc++.h>
#define maxn 50000+5
using namespace std;
int n,s,t[maxn],f[maxn];
int dp[maxn];
int main()
{
memset(dp,0x3f,sizeof dp);
cin>>n>>s;
for(register int i=1;i<=n;i++)
{
cin>>t[i]>>f[i];
t[i]+=t[i-1];f[i]+=f[i-1];
}
dp[0]=0;//需要初始化避免输出极大值
for(register int i=1;i<=n;i++)
{
for(register int j=0;j<i;j++)
{
dp[i]=min(dp[i],dp[j]+t[i]*(f[i]-f[j])+s*(f[n]-f[j]));//前文已详解。
}
}
cout<<dp[n]<<endl;
return 0;
}
迎评:)
——End——