【题解】购物

题目描述

Jesseliu 喜欢购物(是的,千真万确!),他尤其喜欢那种横扫一片商店的快感。最近,他打算对南门口的商店实行他疯狂的购物计划。

南门口的商业区中最繁华的就是黄兴路步行街了。这条街上有 \(n\) 个商店,从 \(1\)\(n\) 编号。Jesseliu 打算进攻 \(m\) 次,每次扫荡第 \(\left[ l_i,r_i\right]\) 个商店,Jesseliu 会把他经过的每一个商店扫荡一空(换句话说,一个商店不会被算作两次)。

因为连续地扫一片商店是很爽的,所以 Jesseliu 把一次扫荡的 happy 值定义为所有连续的一段没有被扫空的商店的 happy 值之和的平方的和,已被扫空的不再计算。

举个栗子
啊栗子真好吃
现在你不经意间得知了 Jesseliu 的购物计划,而你需要将这些计划排序并求出 Jesseliu 最多获得的 happy 值之和,如果算错了 Jesseliu 会卖萌,这可是很致命的哦!

输入格式

第一行为 \(n\)\(m\),意义如描述之所示。

接下来一行 \(n\) 个数,第 \(i\) 个数表示第 \(i\) 个商店的 happy 值。

接下来 \(m\) 行,每行两个数 \(l_i\)\(r_i\) 表示 Jesseliu 第 \(i\) 次行动要扫荡第 \(l_i\) 到第 \(r_i\) 个商店。

输出格式

一行,包含一个数即为 Jesseliu 最多获得的 happy 值之和。

数据范围

测试时间限制 \(1000\,\textrm{ms}\),空间限制 \(512\,\textrm{MiB}\)

  • 对于 \(30\%\) 的数据,\(n\le 10\)\(m\le 8\)
  • 对于 \(60\%\) 的数据,\(n,m\le 1000\)
  • 对于 \(100\%\) 的数据,\(n\le 5000\)\(m\le 10^6\)\(0\le\) happy 值 \(\le 100\)

保证 \(1\)

分析

一道很有意思的 DP 练习题。

\(30\;\mathtt{pts}\)

可以通过全排列获得所有的方案,再逐一计算答案。

复杂度:\(\Theta(m!\times mn)\)

\(60\;\mathtt{pts}\)

我们意识到区间的真实顺序并没有那么重要,更重要的是如何分割区间,使得这个方案是可行的而且是最优的。

同时,我们发现一味地去追求最长的区间并不一定正确。所以我们考虑 DP。

定义 \(f_i\) 为到第 \(i\) 个商店为止,能够产生的最大值。当然如果扫荡区间超过了 \(i\) 也是没有关系的。我们考虑从第 \(f_j\) 个点转移,必须 \(\exists k\quad\left[ i,j\right]\subseteq \left[ l_k,r_k\right]\)

我们只要枚举能到的最远的点,再进行遍历转移。就能够在 \(\Theta(nm)\) 的时间复杂度内完成。

答案:\(f_n\)

\(100\;\mathtt{pts}\)

这个优化实际上很好加。

注意到共有 \(10^6\) 个区间,但是如果区间 \(\left[l_i,r_i\right]\)\(\left[l_j,r_j\right]\) 满足 \(l_i\le l_j\le r_j\le r_i\) (即区间 \(\left[l_j,r_j\right]\subseteq\left[l_i,r_i\right]\))那么区间 \(\left[l_j,r_j\right]\) 就没有用了。因为如果 \(\left[l_j,r_j\right]\) 先,那么答案是不如 \(\left[l_i,r_i\right]\) 先的,同时因为包含关系,所以后面再次扫荡 \(\left[l_j,r_j\right]\) 答案就一定是 \(0\),和不存在是一样的。

这样优化后,以一个商店开始的区间最多只有一个,所以区间数是 \(O(n)\) 的,复杂度就降至 \(\Theta(n^2)\),可以承受。

Code

#include <cstdio>
#include <cctype>
#include <cstring>
using namespace std;

typedef long long ll;
const int max_n = 5000, INF = 0x3f3f3f3f;

ll dp[max_n+1], pref[max_n+1];
int far[max_n], from[max_n], lp[max_n], rp[max_n];

inline ll sq(ll x) { return x * x; }
inline ll my_max(ll a, ll b) { return (a > b)? a:b; }

inline int read()
{
	int ch = getchar(), n = 0, t = 1;
	while (isspace(ch)) { ch = getchar(); }
	if (ch == '-') { t = -1, ch = getchar(); }
	while (isdigit(ch)) { n = n * 10 + ch - '0', ch = getchar(); }
	return n * t;
}

int main()
{
	memset(far, -1, sizeof(far));
	memset(from, 0x3f, sizeof(from));
	
	int n = read(), m = read(), nr_cnt = 0, tl, tr;
	
	pref[0] = 0;
	for (int i = 0; i < n; i++)
	{
		tl = read();
		pref[i+1] = pref[i] + tl;
	}
	for (int i = 0; i < m; i++)
	{
		tl = read(), tr = read();
		
		if (far[tl-1] < tr - 1)
			far[tl-1] = tr - 1;
	}
	
	tl = -1;
	for (int i = 0; i < n; i++)
		if (far[i] > tl)
		{
			tl = far[i];
			lp[nr_cnt] = i, rp[nr_cnt] = far[i];
			nr_cnt++;
		}
	
	for (int i = 0; i < nr_cnt; i++)
		for (int j = lp[i]; j <= rp[i]; j++)
			if (from[j] > lp[i])
				from[j] = lp[i];
	
	dp[0] = 0;
	for (int i = 1; i <= n; i++)
	{
		if (from[i-1] != INF)
		{
			dp[i] = 0;
			
			for (int j = from[i-1]; j < i; j++)
				dp[i] = my_max(dp[i], dp[j] + sq(pref[i] - pref[j]));
		}
		else
			dp[i] = dp[i-1];
	}
	
	printf("%lld\n", dp[n]);
	
	return 0;
}

后记

这道题的难点就是在于想出 \(f_i\) 的状态定义。不要去试图使用哪个有点问题的贪心。

如果想不明白,可以试几组数据来手推一下,说不定就能想明白了。

posted @ 2020-03-01 16:29  5ab  阅读(218)  评论(0编辑  收藏  举报