题意:每个线段有左端点,右端点和积分,找出互不覆盖的线段的积分和的最大值。
和线段覆盖2唯一的区别就在于数据范围变大了,至于它的空间限制缩小对用dp做这个题并没有什么影响。
先说下线段覆盖2的做法:按左右端点排序都可以(线段覆盖4需要按右端点排序,这里按右端点从小到大排序来讲),第一层循环从左到右枚举每个点,第二层循环去枚举右端点比它小的点,找出右端点小于等于它的左端点且dp值最大的点,用它来求出这个点的dp值。这是O(n²)的做法,n在10³左右可以采用。
数据范围增大至10的6次方,那么我们就只能用O(nlogn)及以下的算法,也就是说我们要优化第二层循环,使其能够更快得求出dp值,这里我用了两个优化。
线段覆盖4之所以要按右端点排序是因为这样我们更容易锁定目标点(即右端点小于等于所求点的左端点且dp值最大的点)的大致位置,我们只需要找到右端点小于等于所求点左端点的点里右端点最大的点,目标点就一定在这个点的左边,然后我们再求一下1到这个点的最大dp值就可以求出所求点的dp值了。
上文所说的两个优化:
1、二分求出右端点小于等于所求点左端点的点里右端点最大的点。
2、记录前缀dp值的最大值。
这两个优化都不难,这里就不详述了。细节看代码。
#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
const long long MAXN=1000010;
struct RE
{
long long x,y,w;
}re[MAXN];
long long f[MAXN]={0},mx[MAXN]={0};
long long cmp(const RE &a,const RE &b)
{
return a.y<b.y;
}
long long find(long long l,long long r,long long x)
{
long long mid;
while (l<=r)
{
mid=(l+r)/2;
if (re[mid].y>x) r=mid-1;
else l=mid+1;
}
return r;
}
int main()
{
long long n;
scanf("%lld",&n);
for (long long i=1; i<=n; i++)
scanf("%lld%d%d",&re[i].x,&re[i].y,&re[i].w);
sort(re+1,re+n+1,cmp);
long long ans=0;
for (long long i=1;i<=n;i++)
{
f[i]=mx[find(1,i-1,re[i].x)]+re[i].w;
ans=max(ans,f[i]);
mx[i]=max(f[i],mx[i-1]);
}
printf("%lld",ans);
return 0;
}