2021牛客多校第二场G题

题目:给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

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值