zoukankan      html  css  js  c++  java
  • 【洛谷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}^nmax_{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}^nmax_{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
    ", res[i]);
    		return 0;
    	}
    }
    int main()
    {
    	freopen("1973.in", "r", stdin);
    	return zyt::work();
    }
    
  • 相关阅读:
    网线
    第19次实验
    矩阵乘法
    20次试验
    视频笔记
    1
    effective C++ 条款 34:区分接口继承和实现继承
    effective C++ 条款 35:考虑virtual函数以外的其他选择
    effective C++ 条款 29:为“异常安全”而努力是值得的
    effective C++ 条款 27:尽量少做转型动作
  • 原文地址:https://www.cnblogs.com/zyt1253679098/p/12802617.html
Copyright © 2011-2022 走看看