P1868 【饥饿的奶牛】
线性DP:
题目描述:
给定 n 个区间(任务),任意选择一些区间,使得区间不能重叠,求最大的区间长度和。n=1e5,3e6>=区间端点>=0。
思路:
先贪心一下发现不行,就考虑DP。
题目分析:
对于一个区间来说可以根据选与不选划分,很容易想到可以枚举时间轴,因为这样方便状态转移,如果枚举第i个区间的话不容易找到前一个状态,所以显然要用时间轴,利用差值来转移状态。
状态表示:
f[i]当前时刻为i,从前往后选择区间所得到的最大区间和。
状态转移:
如果当前这个时刻不是某个任务的结尾时刻:
显然状态转移就是 f [ i ] = f [i - 1]。
如果当前这个时刻是某个任务的结尾时刻:
显然状态转移就是 f [ i ] = f [i - len] + len , len为这个任务的持续时间,同时也是选择这个区间的收益。
实现:
代码实现起来可以用二维vector存当前某个时刻为终点的所有区间,但是因为区间长度是3e6,很多编译器可能编译不了。所以开一个固定空间的数组是不稳定的。这里可以用链表实现。
时间复杂度:
总共的区间一共是1e5个,我们遍历时间轴,不论一个右端点有多少个左端点,总共的遍历的区间个数仍是1e5个,时间复杂度是严格O(n)
代码:
#include<bits/stdc++.h>
using namespace std;
const int N=3e6+10;
int f[N],ne[N],h[N],w[N],e[N],idx;
void add(int a,int b,int c)
{
e[idx]=b,ne[idx]=h[a],w[idx]=c,h[a]=idx++;
}
signed main()
{
memset(h, -1, sizeof h);
int n;
int maxlen=3e6+1;
cin>>n;
for(int i=1;i<=n;i++)
{
int x,y;
cin>>x>>y;
add(y,x,y-x+1);//以y结尾的的区间,长度为y-x+1
}
for(int k=0;k<=maxlen;k++)//扫描整个区间
{
f[k]=f[k-1];
for(int i=h[k];~i;i=ne[i])
{
f[k]=max(f[k],f[k-w[i]]+w[i]);
}
}
cout<<f[maxlen]<<endl;
}