[BZOJ2436] [NOI2011] Noi嘉年华 [单调性优化DP]

Link
https://www.lydsy.com/JudgeOnline/problem.php?id=2436


题意
安排 n n n 个活动,每个活动举办时间 ( s i , t i ) (s_i,t_i) (si,ti) ,同一时间两个场地只能有一个办活动(可以办好多个)。
对于每个询问,钦定一个活动必须举办(或者不钦定),要求最大化举办的活动数量较少的场地举办的活动数量。
输出这个最大值。


好 我又老了

上来先排序离散化
考虑到实际上一个会场选的是“一段时间”,可以预处理出在某一段时间内开始并结束的活动数量 p ( a , b ) p(a,b) p(a,b)
很容易根据上面这东西大概地对转移有个猜想,然后就差不多可以设计出状态:
可以考虑设类似于 f ( k , i ) f(k,i) f(k,i) ,那么前 k k k 个时间,一个场地至多选 i i i 个的时候另一个场地至多选 f ( k , i ) f(k,i) f(k,i)
为了方便钦定,相应地考虑可能会设 g ( k , i ) g(k,i) g(k,i) ,那么第 k k k 个时间之后,一个场地至多选 i i i 个的时候下略

考虑转移, f ( k , i ) = max ⁡ d ∈ [ 0 , k ) max ⁡ { f ( d , i ) + p ( d , k ) , f ( d , i − p ( d , k ) ) } f(k,i)=\max\limits_{d\in[0,k)}\max\{ f(d,i)+p(d,k),f(d,i-p(d,k))\} f(k,i)=d[0,k)maxmax{f(d,i)+p(d,k),f(d,ip(d,k))}
那么 f f f g g g 都可以 O ( n 3 ) O(n^3) O(n3) 搞出来
然后考虑不加限制时的答案:暴扫一遍都可以搞出来了(。。。


钦定一个活动 等于 钦定了那一整段时间内的都要选
然后两边的怎么做?
如果我们钦定的给某一个场地办,对于另外一个场地可以考虑枚举在钦定左右分别选了几个
好像也搞完了?
a n s ( i , j ) = max ⁡ x , y { min ⁡ { x + y , f ( i , x ) + p ( i , j ) + g ( j , y ) } } ans(i,j)=\max\limits_{x,y}\{\min\{x+y,f(i,x)+p(i,j)+g(j,y)\}\} ans(i,j)=x,ymax{min{x+y,f(i,x)+p(i,j)+g(j,y)}}
O ( n 3 ) O(n^3) O(n3) ……?不对。

最终答案并不等于 a n s ( s i , t i ) ans(s_i,t_i) ans(si,ti)
(啊啊啊。。要是考起来我大概就被这东西坑爆了)

钦定了 ( L , R ) (L,R) (L,R) ,但是实际上选的区域可能会更大 比如 ( l , r ) , l &lt; L &lt; R &lt; r (l,r),l&lt;L&lt;R&lt;r (l,r),l<L<R<r 这样
显然我们钦定还是有影响到 ( l , L ) (l,L) (l,L) ( R , r ) (R,r) (R,r)
所以我们需要处理出所有的 a n s ( i , j ) ans(i,j) ans(i,j) ,实际上是 O ( n 4 ) O(n^4) O(n4)
然后可以搞记忆化搞剪枝等等玄学一发小常数骗分(甚至可能水掉这道题
(我作为一个不会水法选手 记笔记记笔记.jpg
(但是感觉我反而会玄学剪枝剪到常数变大啊(???

关于这部分中间差点掉坑,其实如果运气好是可以避免的
思考钦定某个活动要办,然后考虑枚举一下某个场地覆盖这个活动的那段启用的时间
好像也挺自然的(?)


怎么优化成 O ( n 3 ) O(n^3) O(n3)
dp 优化当然要考虑单调性啦,考虑 a n s ( i , j ) = max ⁡ x , y min ⁡ ans(i,j)=\max\limits_{x,y}\min ans(i,j)=x,ymaxmin
思考 x , y x,y x,y 最优决策点,那先固定 i , j i,j i,j
不难发现 f ( i , x ) f(i,x) f(i,x) g ( j , y ) g(j,y) g(j,y) i i i 固定的时候有显然的单调性
换成 a n s ( i , j ) = max ⁡ x , y { min ⁡ { f ( i , x ) + g ( j , y ) , x + p ( i , j ) + y } } ans(i,j)=\max\limits_{x,y}\{\min\{f(i,x)+g(j,y),x+p(i,j)+y\}\} ans(i,j)=x,ymax{min{f(i,x)+g(j,y),x+p(i,j)+y}}
所以对于最优决策,按照 x , y x,y x,y 中的某一个排序后,按顺序看 x , y x,y x,y 必定是一个变大一个变小
那么维护指针就可以 O ( n ) O(n) O(n) 找出所有最优决策点对 ( x , y ) (x,y) (x,y)
复杂度 O ( n 3 ) O(n^3) O(n3)


#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<ctime>
using namespace std;
const int MAXN = 405;
int n, s[MAXN], t[MAXN], tmp[MAXN], m;
int f[MAXN][MAXN], p[MAXN][MAXN], ans[MAXN][MAXN], g[MAXN][MAXN];
int main()
{
	scanf("%d", &n);
	for (register int i = 1; i <= n; ++i)
	{
		scanf("%d%d", &s[i], &t[i]);
		t[i] += s[i];
		tmp[++m] = s[i], tmp[++m] = t[i];
	}
	sort(tmp + 1, tmp + 1 + m);
	m = unique(tmp + 1, tmp + 1 + m) - tmp - 1;
	for (register int i = 1; i <= n; ++i)
	{
		s[i] = lower_bound(tmp + 1, tmp + 1 + m, s[i]) - tmp;
		t[i] = lower_bound(tmp + 1, tmp + 1 + m, t[i]) - tmp;
		for (register int l = 1; l <= s[i]; ++l)
		for (register int r = t[i]; r <= m; ++r)
			++p[l][r];
	}
	for (register int i = 1; i <= m; ++i)
	for (register int j = 1; j <= n; ++j)
		f[i][j] = g[i][j] = -0x3f3f3f3f;
	for (register int k = 1; k <= m; ++k)
	for (register int i = 0; i <= p[1][k]; ++i)
	for (register int d = 1; d <= k; ++d)
	{
		f[k][i] = max(f[k][i], f[d][i] + p[d][k]);
		if (i >= p[d][k]) f[k][i] = max(f[k][i], f[d][i-p[d][k]]);
	}
	for (register int k = m; k >= 1; --k)
	for (register int i = 0; i <= p[k][m]; ++i)
	for (register int d = k; d <= m; ++d)
	{
		g[k][i] = max(g[k][i], g[d][i] + p[k][d]);
		if (i >= p[k][d]) g[k][i] = max(g[k][i], g[d][i-p[k][d]]);
	}
	for (register int i = 1; i <= m; ++i)
	for (register int j = i + 1; j <= m; ++j)
	for (register int fa, c, x = 0, y = n; x <= n; ++x)
	{
		fa = min(x + p[i][j] + y, f[i][x] + g[j][y]);
		while (y)
		{
			c = min(x + p[i][j] + y - 1, f[i][x] + g[j][y - 1]);
			if (fa <= c) fa = c, --y;
			else break;
		}
		ans[i][j] = max(ans[i][j], min(x + p[i][j] + y, f[i][x] + g[j][y]));
	}
	int Ans = 0;
	for (register int i = 1; i <= n; ++i) Ans = max(Ans, min(f[m][i], i));
	printf("%d\n", Ans);
	for (register int k = 1; k <= n; ++k)
	{
		Ans = 0;
		for (register int i = 1; i <= s[k]; ++i)
		for (register int j = m; j >= t[k]; --j)
			Ans = max(Ans, ans[i][j]);
		printf("%d\n", Ans);
	}
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值