题目:给n个[l,r]的区间(区间长度为r-l),将n个区间分为k组,要求每组至少有1的重叠(可以一个区间为一组),求k组重叠的和的最大值
用动态规划(以下内容为别人教授,非自己思考所得,该文章仅为自己留作参考)
首先考虑那些包含其他区间的大区间,有两种可能
1.单独为一组
2.和大区间包含的那个小区间的区间为一组
这里我们举一个例子:
下面abcd四条线段,要将他们分为两组,取最大值
如果是第2种情况,显然就不用考虑大区间,只用考虑小区间
就比如a,b,c,d四条线段,a包含b,c包含d,这样如果是对于将这四条线段分成两组的情况,只用考虑d和d之间的交集即可
而如果是第1种情况,a区间单独为一组,则就是考虑b,c,d之间的交集,显然,b,c,d的交集又之和b,d有关,所以就不需要c(如果将c列为一组同理,比较二者的大小取大的就行)
因此无论那种情况我们都不需要保留大区间,只需要一个数组存下大区间的长度即可
然后再最后进行dp比较取最大值就行(这是代码的第三部分)
(第一部分是如何将大区间小区间分开,第二部分就是dp)
然后我们来考虑去除掉大区间之后的情况
首先将剩下的以左边界进行一个排序,无非有以下两种情况
当然哪种情况都不重要,我们现在来推状态方程
dp[i][j]代表前i个区间组成了j组所能构成的最大重叠数目
如图:
假设现在已经知道了dp[i][j],即已经遍历完前i组数据,然后我们发现i+1个区间和第k个区间有交集,那么我们就考虑能不能把i+1和k两个区间放在一组,如此到k个区间构成j+1各组的方程就是
dp[k][j+1]=dp[i][j]+b[i+1]-a[k]
因为不一定只有一个k,所以我们取最大值就行了(注意这里对于dp[i][j]来说,a[k]是不变的,所以取最大值的只是前两个数)
至此,这道题就基本结束了
实际上上面的dp是一个3次的维度,我们考虑能不能优化
用优先队列,存下对于每个j来说的dp[i][j]+b[i+1],比较大小后取最大值作为dp得到的值,因为要保证b[i+1]>a[k],所以要将b[i+1]的值也存下来,取优先队列中最大的并且符合b[i+1]>a[k]的值
如果我们发现优先队列为空,说明没有线段满足条件,则dp[i][j]=0,0即是说明该情况不存在
这样能将复杂度优化到n²logn的程度
讲的有点乱(毕竟不是自己想出来的),还是看代码吧,代码有注释
#include <bits/stdc++.h>
using namespace std;
#define N 5010
int dp[N][N];
int bh[N];//放大区间的长度
struct Internal
{
int a, b;
}inter[N];//放小区间的左右端点
bool cmp1(Internal x, Internal y)
{
if (x.a == y.a)
{
return x.b > y.b;
}
return x.a < y.a;
}
bool cmp2(int a, int b)
{
return a > b;
}
struct Node
{//大顶堆
int x, y;//优先队列的储存,x用来排序,y用来比较看if要出队
bool operator < (const Node & obj) const {
return x < obj.x;
}
};
priority_queue<Node> q[N];//要不就这样写
int main()
{
int n, k;
cin >> n >> k;
int cnt = 0;//cnt是从1开始记得,因为要求前缀和
for (int i = 1; i <= n; i++)
{
cin >> inter[i].a >> inter[i].b;
}
sort(inter+1, inter + n+1, cmp1);//从小到大排序
int minn = 0x3f3f3f3f;//当前右边界
for (int i = n; i >0; i--)
{
if (inter[i].b >= minn)//如过这个区域的右边界比当前右边界大,则说明区间包含(排序的时候已经保证左边界是越来越小)
{
bh[++cnt] = inter[i].b - inter[i].a;
inter[i].a = 0x3f3f3f3f;//将区域去除
}
else
{
minn = inter[i].b;//更新当前右边界
}
}
sort(inter+1, inter + n+1, cmp1);//将大区域去除
n -= cnt;
for (int i = 1; i <= n; i++)//开始dp
{
for (int j = i; j > 0; j--)
{
if (j == 1)//全部分成一组
{
if (inter[i].a < inter[1].b)//如果最后和第一个有交集,就更新
{
dp[i][j] = inter[1].b - inter[i].a;
}
else//忘了
{
dp[i][j] = 0;
}
}
else
{
//是j-1,因为dp公式是dp[i][j]=max(dp[k][j-1]+b[k+1])-a[k]
while (q[j-1].size() && q[j-1].top().y <= inter[i].a) //如果没有交集,就弹出
{
q[j-1].pop();
}
if (q[j-1].size())//如果把小的都弹出了之后还不为空
{
dp[i][j] = q[j-1].top().x - inter[i].a;
}
else
{
dp[i][j] = 0;//说明没有情况能使前i个区域分成j组
}
}
if (i < n && dp[i][j])
{
if (inter[i+1].b>inter[i].a)//满足状态方程
{
q[j].push({ dp[i][j] + inter[i + 1].b,inter[i + 1].b });
}
}
}
}
int ans = 0;//结果
sort(bh+1, bh + cnt+1,cmp2);
for (int i = 0; i <=min(cnt,k-1); i++)//必须从0开始,因为可能不加大区间最大,就没有大区间的情况
{
if (i)
{
bh[i] += bh[i - 1];//前缀和
}
if (dp[n][k - i])//只要前n组能构成k-i组,就把i个大区间前缀和单独成组,加进去
{
ans = max(dp[n][k - i] + bh[i], ans);
}
}
cout << ans << endl;
return 0;
}
最后,膜拜解题大佬,并且感谢把我讲明白的队友OTZ