写在前面
一开始在打尼克的任务的时候,并没有特别清晰的思路,想的是由于之前写的 D P DP DP的习惯,从前向后遍历,但是,一直就是设的状态不会进行转移,之后又有考虑从后向前遍历,但是由于自己对于 D P DP DP的不理解,所以还是不会写出状态转移方程。
所以就是,因为自己对于DP的状态的设置以及状态转移方程的不熟悉,导致了自己并不会写。
那么下一步的计划就是要深入理解 D P DP DP的思想,以及转移的方法。
正文
题目描述
饥饿的奶牛
描述 D e s c r i p t i o n Description Description
牛在饲料槽前排好了队。饲料槽依次用1到N(1<=N<=2000)编号。每天晚上,一头幸运的牛根据约翰的规则,吃其中一些槽里的饲料。
约翰提供B个区间的清单。一个区间是一对整数start-end,1<=start<=end<=N,表示一些连续的饲料槽,比如1-3,7-8,3-4等等。牛可以任意选择区间,但是牛选择的区间不能有重叠。
当然,牛希望自己能够吃得越多越好。给出一些区间,帮助这只牛找一些区间,使它能吃到最多的东西。
在上面的例子中,1-3和3-4是重叠的;聪明的牛选择{1-3,7-8},这样可以吃到5个槽里的东西。
尼克的任务
描述 D e s c r i p t i o n Description Description
尼克每天上班之前都连接上英特网,接收他的上司发来的邮件,这些邮件包含了尼克主管的部门当天要完成的全部任务,每个任务由一个开始时刻与一个持续时间构成。
尼克的一个工作日为N分钟,从第一分钟开始到第N分钟结束。当尼克到达单位后他就开始干活。如果在同一时刻有多个任务需要完成,尼克可以任选其中的一个来做,而其余的则由他的同事完成,反之如果只有一个任务,则该任务必需由尼克去完成,假如某些任务开始时刻尼克正在工作,则这些任务也由尼克的同事完成。如果某任务于第P分钟开始,持续时间为T分钟,则该任务将在第P+T-1分钟结束。
写一个程序计算尼克应该如何选取任务,才能获得最大的空暇时间。。
相似性
- 对于两道题而言,都要求的是所选取的区间不能有重叠;
- 都算是一类题:求得是区间覆盖的最值问题。
不同点
- 根据题目意思而言,饥饿的奶牛中求的是最大区间覆盖,尼克的任务中求的是最小的区间覆盖;
- 区间选取的规定也不一样,饥饿的奶牛中是任意选取区间,随便哪个区间,选区间或者是不选区间都是可以的,但是尼克的任务中则是,在当前时间段内若只有一个区间就必须要选取,没有其他的选择,他的选择是有限制的。
因为我首先做的是尼克的任务,所以我要先说一下尼克的任务的思路。
尼克的任务 的 思路
1.因为当前点的空暇时间不仅和前面的区间的选取有关还是和之后的区间的选取有关的,所以在 D P DP DP的遍历中,我们是不能确定所处在当前点的最大价值,因为可能在之后的区间选取之后会影响到该点的价值,也就是会有后效性。所以,我们考虑从后往前遍历。
2.我们令
f
[
i
]
f[i]
f[i]表示的是从
n
n
n到
i
i
i这个节点(这里指的并不是区间)的最大空暇时间。令一个区间的起点和终点为
s
,
t
s,t
s,t。
那么状态转移方程为:
- f [ i ] = f [ i + 1 ] f[i]=f[i+1] f[i]=f[i+1](当i不是区间的起点的时候需要继承上一个的状态)
- f [ i ] = m a x ( f [ i ] , f [ t + 1 ] + t − i + 1 ) f[i]=max(f[i],f[t+1]+t-i+1) f[i]=max(f[i],f[t+1]+t−i+1) (当i是区间的起点时,我们就需要判断到底是选择哪一个区间是最优的,没有不选区间的状态。这里的t的取值应该是所有的以i为起点的终点)
3.最后,确定我们的初始状态是f[i]=0,目标状态是f[1]。
那么,我们按照同样的思路考虑一下饥饿的奶牛。
饥饿的奶牛 的 思路
一开始的时候,我觉得饥饿的奶牛和尼克的任务一毛一样,之后有思考了一下,发现确实一毛一样。(这确实也反映出来了我的问题,看题是有一定的思维漏洞的)所以,就挂掉了。。。
下面来说一下我的正确的和上面相似的思路:
1.考虑从后往前遍历;
2.我们令
f
[
i
]
f[i]
f[i]表示的是从
n
n
n到
i
i
i这个节点(这里指的并不是区间)的最大区间覆盖的价值。令一个区间的起点和终点为
s
,
t
s,t
s,t。
那么状态转移方程为:
- f [ i ] = m a x ( f [ i ] , f [ t + 1 ] + t − i + 1 ) f[i]=max(f[i],f[t+1]+t-i+1) f[i]=max(f[i],f[t+1]+t−i+1) (当i是区间的起点时,我们就需要判断到底是选择哪一个区间是最优的,这里的t的取值同样是所有的以i为起点的终点)
- f [ i ] = m a x ( f [ i + 1 ] , f [ i ] ) f[i]=max(f[i+1],f[i]) f[i]=max(f[i+1],f[i]) (但是,这里和上面不一样的是:在进行上面的操作之后,我们还要比较一下和 f [ i + 1 ] f[i+1] f[i+1]的大小,这里是因为对于有区间覆盖的 i i i节点,我们有两个合法的状态:选和不选。)
3.最后,确定我们的初始状态是f[i]=0,目标状态是 m a x ( f [ i ] ) max(f[i]) max(f[i])。(这里也是有区别的,跟上面区别的是,我们并不是有区间就要选择。所以,我们并不是非要停留在第一个节点,而是随时可以在任意一个节点结束自己的区间覆盖。)
code
饥饿的奶牛
#include<bits/stdc++.h>
using namespace std;
const int nn=2002;
int n,si=100000000,ti,ans=0;
struct task
{
int x,y;
}t[nn];
int f[nn],sum[nn];
inline int read()
{
int x=0,f=1; char ch=getchar();
while(ch>'9'||ch<'0'){if(ch=='-') f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=x*10+ch-'0'; ch=getchar();}
return x*f;
}
bool cmp(task x,task y)
{
return x.x>y.x;
}
int main()
{
n=read();
for(int i=1;i<=n;++i)
{
t[i]=(task){read(), read()};
if(t[i].x>t[i].y) swap(t[i].x,t[i].y);/**/
sum[t[i].x]++;
ti=max(t[i].y,ti);
si=min(t[i].x,si);
}
sort(t+1,t+1+n,cmp);
int cnt=0;
for(int i=ti;i>=si;--i)
{
if(sum[i])
for(int j=1;j<=sum[i];++j)
f[i]=max(f[i],f[t[++cnt].y+1]+t[cnt].y-t[cnt].x+1);
f[i]=max(f[i],f[i+1]);/*对于选择区间和不选择区间中进行了一个比较*/
ans=max(ans,f[i]);
}
printf("%d\n",ans);
return 0;
}
尼克的任务
#include<bits/stdc++.h>
using namespace std;
const int nn=10002;
int n,k;
struct task
{
int x,y;
}t[nn];
int f[nn],sum[nn];
inline int read()
{
int x=0,f=1; char ch=getchar();
while(ch>'9'||ch<'0'){if(ch=='-') f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=x*10+ch-'0'; ch=getchar();}
return x*f;
}
bool cmp(task x,task y)
{
return x.x>y.x;
}
int main()
{
n=read(), k=read();
for(int i=1;i<=k;++i) t[i]=(task){read(), read()}, sum[t[i].x]++;
sort(t+1,t+1+k,cmp);
int cnt=0;
for(int i=n;i>=1;--i)
{
if(sum[i])
for(int j=1;j<=sum[i];++j)
f[i]=max(f[i],f[i+t[++cnt].y]);
else f[i]=f[i+1]+1;
cout<<i<<' '<<f[i]<<endl;
}
printf("%d\n",f[1]);
return 0;
}
小注:并不是说饥饿的奶牛也是有后效性,只是我刚好用处理尼克的任务的方法处理了饥饿的奶牛。
这两个总结下来:就是很相似,但是细节处理上也有很多的不同点。
注意审题,注意审题,注意审题!!!
完~