★【动态规划】【单调性优化】【NOI2011】NOI嘉年华

【问题描述】
NOI2011 在吉林大学开始啦!为了迎接来自全国各地最优秀的信息学选手,吉林大学决定举办两场盛大的 NOI 嘉年华活动,分在两个不同的地点举办。每个嘉年华可能包含很多个活动,而每个活动只能在一个嘉年华中举办。
现在嘉年华活动的组织者小安一共收到了 n 个活动的举办申请,其中第 i 个活动的起始时间为 Si,活动的持续时间为 Ti。这些活动都可以安排到任意一个嘉年华的会场,也可以不安排。
小安通过广泛的调查发现,如果某个时刻,两个嘉年华会场同时有活动在进行(不包括活动的开始瞬间和结束瞬间),那么有的选手就会纠结于到底去哪个会场,从而变得不开心。所以,为了避免这样不开心的事情发生,小安要求不能有两个活动在两个会场同时进行(同一会场内的活动可以任意进行)。
另外,可以想象,如果某一个嘉年华会场的活动太少,那么这个嘉年华的吸引力就会不足,容易导致场面冷清。所以小安希望通过合理的安排,使得活动相对较少的嘉年华的活动数量最大。
此外,有一些活动非常有意义,小安希望能举办,他希望知道,如果第 i 个活动必须举办(可以安排在两场嘉年华中的任何一个),活动相对较少的嘉年华的活动数量的最大值。
【输入格式】
从文件 show.in 中读入数据。
输入的第一行包含一个整数 n,表示申请的活动个数。
接下来 n 行描述所有活动,其中第 i 行包含两个整数 Si、Ti,表示第 i 个活动从时刻 Si 开始,持续 Ti 的时间。
【输出格式】
输出到文件 show.out 中。
输出的第一行包含一个整数,表示在没有任何限制的情况下,活动较少的嘉年华的活动数的最大值。
接下来 n 行每行一个整数,其中第 i 行的整数表示在必须选择第 i 个活动的前提下,活动较少的嘉年华的活动数的最大值。
【评分标准】
对于一个测试点:
如果输出格式不正确(比如输出不足 n+1 行),得 0 分;
如果输出文件第一行不正确,而且后 n 行至少有一行不正确,得 0 分;
如果输出文件第一行正确,但后 n 行至少有一行不正确,得 4 分;
如果输出文件第一行不正确,但后 n 行均正确,得 6 分;
如果输出文件中的 n+1 行均正确,得 10 分。
【样例输入】
5
8 2
1 5
5 3
3 2
5 3

【样例输出】
2
2
1
2
2
2

【样例说明】
在没有任何限制的情况下,最优安排可以在一个嘉年华安排活动 1, 4,而在另一个嘉年华安排活动 3, 5,活动 2 不安排。
【数据规模与约定】
所有测试数据的范围和特点如下表所示

此题考察动态规划及其单调性优化。
区间离散化,设A = {嘉年华1的活动}, B = {嘉年华2的活动},C = {未安排的活动}。
设num[i][j]为包含在区间[i, j]中的区间个数;
pre[i][j]为在区间[0, i]中放入j个区间到B后,最多能放入A的个数;
suf[i][j]为在区间[i, +∞)中放入j个区间到B后,最多能放入A的个数。

那么有以下两个转移方程:



当然程序中用了向后递推的方法。

第一问就很简单地算出来了,即:



对于第二问,先考虑朴素做法:
设g为满足条件的最大方案。
那么:

所以最终答案为:


现在考虑优化:
对于确定的i, j,记

那么则有:随着x的增大,使得g(i, j)最大的y是逐渐减小的。
于是对于g的计算可以优化到O (n ^ 3)
Accode:

#include <cstdio>
#include <cstdlib>
#include <algorithm>
#include <cstring>
#include <string>
using namespace std;
const int maxN = 410, INF = 0x3f3f3f3f;
int pre[maxN][maxN], suf[maxN][maxN], g[maxN][maxN];
int num[maxN][maxN], tab[maxN], L[maxN], R[maxN], n, m;

int main()
{
	freopen("show.in", "r", stdin);
	freopen("show.out", "w", stdout);
	scanf("%d", &n);
	for (int i = 0; i < n; ++i)
	{
		scanf("%d%d", L + i, R + i);
		tab[i << 1] = L[i];
		tab[(i << 1) + 1] = R[i] += L[i];
	}
	sort(tab, tab + (n << 1));
	int cnt = unique(tab, tab + (n << 1)) - tab;
	//unique函数,可以将有序的列表去重。
	//这里的cnt为最终离散化过后的区间右界。
	for (int i = 0; i < n; ++i)
		L[i] = lower_bound(tab, tab + cnt, L[i]) - tab,
		R[i] = lower_bound(tab, tab + cnt, R[i]) - tab;
	//离散化过程。
	for (int i = 0; i < cnt; ++i)
	{
		for (int j = 0; j < n; ++j)
			if (L[j] >= i) ++num[i][R[j]];
		for (int j = i + 1; j < cnt; ++j)
			num[i][j] += num[i][j - 1];
	}
	memset(pre, ~0x3f, sizeof pre); pre[0][0] = 0;
	memset(suf, ~0x3f, sizeof suf); suf[cnt - 1][0] = 0;
	for (int i = 0; i < cnt; ++i)
	{
		for (int j = 0; j < n + 1; ++j)
		if (pre[i][j] > ~INF)
			pre[i][pre[i][j]] = max(pre[i][pre[i][j]], j);
		//这里是将pre设一个初始值。
		for (int j = n - 1; j > -1; --j)
			pre[i][j] = max(pre[i][j], pre[i][j + 1]);
		for (int j = 0; j < n + 1; ++j)
		for (int k = i + 1; k < cnt; ++k)
		if (pre[i][j] > ~INF)
			pre[k][pre[i][j]] = max(pre[k][pre[i][j]], j + num[i][k]);
		//向后递推,相当于在B中放入了pre[i][j]个区间,
		//在A中放入了j + num[i][k]个区间。
	}
	for (int i = cnt - 1; i > -1; --i)
	{
		for (int j = 0; j < n + 1; ++j)
		if (suf[i][j] > ~INF)
			suf[i][suf[i][j]] = max(suf[i][suf[i][j]], j);
		for (int j = n - 1; j > -1; --j)
			suf[i][j] = max(suf[i][j], suf[i][j + 1]);
		for (int j = 0; j < n + 1; ++j)
		for (int k = i - 1; k > -1; --k)
		if (suf[i][j] > ~INF)
			suf[k][suf[i][j]] = max(suf[k][suf[i][j]], j + num[k][i]);
	}
	for (int i = 0; i < cnt; ++i)
	for (int j = i; j < cnt; ++j)
	{
		g[i][j] = ~INF;
		for (int x = 0, y = n; x < n + 1; ++x)
		{
			while (y > -1 && x + y > num[i][j] + pre[i][x] + suf[j][y]) --y;
			//让x + y(即放入B的个数)刚刚小于
			//num[i][j] + pre[i][x] + suf[j][y](即放入A的个数),
			//这时的g值最大。
			if (y > -1) g[i][j] = max(g[i][j], x + y); //
		}
	}
	int ans = 0;
	for (int i = 0; i < n + 1; ++i)
		ans = max(ans, min(i, suf[0][i])); //
	printf("%d\n", ans);
	for (int i = 0; i < n; ++i)
	{
		int ans = 0;
		for (int j = 0; j < L[i] + 1; ++j)
		for (int k = R[i]; k < cnt; ++k)
			ans = max(ans, g[j][k]);
		printf("%d\n", ans);
	}
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值