【洛谷1973】[NOI2011]NOI嘉年华(动态规划)

我自闭了啊。

这么裸的、没什么知识点和技巧的题我竟然想不出来 ……

(熊猫拍桌子)

题目

洛谷 1973

分析

以下 \(s_i\)\(t_i\) 分别表示活动 \(i\) 的开始时刻和结束时刻。时刻均离散化成 \([1,2n]\) 中的整数。活动「在某个时间区间中」指活动被该区间完全包含。

我的第一想法是把活动按结束时间排序, \(f_{i,j,a,b}\) 表示考虑到第 \(i\) 个活动,当前第一个会场有 \(j\) 个活动,两个会场的最后一个活动分别是 \(a\)\(b\) 时第二个会场的最大值。\(O(1)\) 决策第 \(i\) 个活动是放在哪个会场或者不选。单次时间复杂度 \(O(n^4)\) ,总时间复杂度 \(O(n^5)\)

这个通过做题的经验而不是理性分析设计出的方案是很臃肿的。考虑需要知道什么信息才能让其他事情都不影响后面的决策。如果当前选择的所有活动都在 \([1,t]\) 中,那么无论这些活动如何安排,都不影响 \((t,2n]\) 中的活动决策。剩下的活动都是跨过 \(t\) 的,不妨直接钦定 \(t\) 对应的状态不能选跨过 \(t\) 的。这样就设计出了一个新的状态:\(f_{i,j}\) 表示已选活动都在 \([1,i]\) 中,第一个会场有 \(j\) 个活动时第二个会场的最大值。枚举上一个没有被跨过的位置,就有如下转移方程:

\[f_{i,j}=\max_{k=0}^{i-1}\max(f_{k,j}+c_{k+1,i},f_{k,j-c_{k+1,i}}) \]

\(c_{l,r}\) 表示 \([l,r]\) 中的活动数。这个方程的意义是决策 \((k,i]\) 中的活动放在第一个还是第二个会场。显然,把这些活动全部举办不会比只举办一部分劣。这样,就在 \(O(n^3)\) 的时间内解决了没有限制某个活动必选时的答案。

钦定必须选某个活动,就相当于转移时不能转移到这个活动的内部(因为已经规定了不选跨过转移点的活动)。也就是说,这种限制的影响范围只局限于这个活动左右两侧最近的转移点之间。因此,再维护 \(g_{i,j}\) 表示已选活动都在 \([i,2n]\) 中,第一个会场有 \(j\) 个活动时第二个会场的最大值。求法和 \(f\) 类似。

这样,直接枚举被钦定的活动左右两侧最近的转移点即可。因为两个会场没有区别,所以一定存在一种最优方案使得这个被钦定的活动在第一个会场:

\[ans=\max_{l=0}^{s_i-1}\max_{r=t_j+1}^{2n}\max_{x=0}^n\max_{y=0}^n \min(x+y+c_{l+1,r-1},f_{l,x}+g_{r,y}) \]

这个单次时间复杂度是 \(O(n^4)\) 总复杂度 \(O(n^5)\) 。注意到从第三层 max 开始后面的内容就和 \(s_i\)\(t_i\) 无关了,因此可以预处理:

\[h_{l,r}=\max_{x=0}^n\max_{y=0}^n \min(x+y+c_{l+1,r-1},f_{l,x}+g_{r,y}) \]

\[ans=\max_{l=0}^{s_i-1}\max_{r=t_j+1}^{2n}h_{l,r} \]

这样时间复杂度瓶颈就是算 \(h\) 时的 \(O(n^4)\) 了。

\(i\) 一定时, \(f_{i,x}\) 是关于 \(x\) 的非增函数,\(g_{i,y}\) 是关于 \(y\) 的非增函数(因为在第一个会场去掉一个活动不影响第二个会场方案的合法性,即 \(f_{i,x}\leq f_{i,x-1},g_{i,y}\leq g_{i,y-1}\))。

\[F(y)=x+y+c_{l+1,r-1} \]

\[G(y)=f_{l,x}+g_{r,y} \]

\[F'(y)=x'+y+c_{l+1,r-1} \]

\[G'(y)=f_{l,x'}+g_{r,y} \]

其中 \(x'>x\) 。那么 \(F(y)\)\(F'(y)\) 是增函数,根据上面的结论可以得出 \(G(y)\)\(G'(y)\) 是减函数。画个草图:

(事实上不一定是直线,但就是这个道理)

也就是说当 \(x\) 变大时,使 \(\min(F(y),G(y))\) 最大的 \(y\) (即交点)是在变小。那么就不需要枚举 \(y\) ,利用单调性用指针从大往小扫就行了。时间复杂度 \(O(n^3)\)

代码

注意 \(\min(F(y),G(y))\) 有可能有连续的一段函数值相同,所以减少 \(y\) 的时候只要减少后不会变差就必须减少。

#include <cstdio>
#include <algorithm>
#include <cstring>
#include <cmath>
using namespace std;

namespace zyt
{
	const int N = 210, T = N << 1;
	int n, m, s[N], t[N], pre[T][N], suf[T][N], ans[T][T], cnt[T][T], tmp[T];
	int work()
	{
		scanf("%d", &n);
		for (int i = 1; i <= n; i++)
			scanf("%d%d", &s[i], &t[i]), t[i] += s[i] - 1, tmp[++m] = s[i], tmp[++m] = t[i];
		sort(tmp + 1, tmp + m + 1);
		m = unique(tmp + 1, tmp + m + 1) - tmp - 1;
		for (int i = 1; i <= n; i++)
		{
			s[i] = lower_bound(tmp + 1, tmp + m + 1, s[i]) - tmp;
			t[i] = lower_bound(tmp + 1, tmp + m + 1, t[i]) - tmp;
		}
		for (int i = 1; i <= m; i++)
			for (int j = i; j <= m; j++)
				for (int k = 1; k <= n; k++)
					if (s[k] >= i && t[k] <= j)
						++cnt[i][j];
		memset(pre, -1, sizeof(pre));
		pre[0][0] = 0;
		for (int i = 1; i <= m; i++)
			for (int j = 0; j <= n; j++)
				for (int k = 0; k < i; k++)
				{
					if (cnt[k + 1][i] <= j && ~pre[k][j - cnt[k + 1][i]])
						pre[i][j] = max(pre[i][j], pre[k][j - cnt[k + 1][i]]);
					if (~pre[k][j])
						pre[i][j] = max(pre[i][j], pre[k][j] + cnt[k + 1][i]);
				}
		memset(suf, -1, sizeof(suf));
		suf[m + 1][0] = 0;
		for (int i = m; i > 0; i--)
			for (int j = 0; j <= n; j++)
				for (int k = m + 1; k > i; k--)
				{
					if (cnt[i][k - 1] <= j && ~suf[k][j - cnt[i][k - 1]])
						suf[i][j] = max(suf[i][j], suf[k][j - cnt[i][k - 1]]);
					if (~suf[k][j])
						suf[i][j] = max(suf[i][j], suf[k][j] + cnt[i][k - 1]);
				}
		for (int i = 1; i <= m; i++)
			for (int j = i; j <= m; j++)
			{
				int y = n;
				for (int x = 0; x <= n; x++)
				{
					if (pre[i - 1][x] == -1)
						break;
					while (y > 0 && (suf[j + 1][y] == -1 || 
						min(pre[i - 1][x] + suf[j + 1][y], x + y + cnt[i][j]) <= 
						min(pre[i - 1][x] + suf[j + 1][y - 1], x + y + cnt[i][j] - 1)))
						--y;
					if (~suf[j + 1][y])
						ans[i][j] = max(ans[i][j], min(pre[i - 1][x] + suf[j + 1][y], x + y + cnt[i][j]));
				}
			}
		static int res[N];
		for (int i = 1; i <= n; i++)
		{
			for (int l = 1; l <= s[i]; l++)
				for (int r = t[i]; r <= m; r++)
					res[i] = max(res[i], ans[l][r]);
			res[0] = max(res[0], res[i]);
		}
		for (int i = 0; i <= n; i++)
			printf("%d\n", res[i]);
		return 0;
	}
}
int main()
{
	freopen("1973.in", "r", stdin);
	return zyt::work();
}
posted @ 2020-04-29 15:49  Inspector_Javert  阅读(149)  评论(0编辑  收藏  举报