题目描述
Jesseliu 喜欢购物(是的,千真万确!),他尤其喜欢那种横扫一片商店的快感。最近,他打算对南门口的商店实行他疯狂的购物计划。
南门口的商业区中最繁华的就是黄兴路步行街了。这条街上有 (n) 个商店,从 (1) 到 (n) 编号。Jesseliu 打算进攻 (m) 次,每次扫荡第 (left[ l_i,r_i ight]) 个商店,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\, extrm{ms}),空间限制 (512\, extrm{MiB})。
- 对于 (30\%) 的数据,(nle 10),(mle 8);
- 对于 (60\%) 的数据,(n,mle 1000);
- 对于 (100\%) 的数据,(nle 5000),(mle 10^6),(0le) happy 值 (le 100)。
保证 (1)
分析
一道很有意思的 DP 练习题。
(30;mathtt{pts})
可以通过全排列获得所有的方案,再逐一计算答案。
复杂度:(Theta(m! imes mn))
(60;mathtt{pts})
我们意识到区间的真实顺序并没有那么重要,更重要的是如何分割区间,使得这个方案是可行的而且是最优的。
同时,我们发现一味地去追求最长的区间并不一定正确。所以我们考虑 DP。
定义 (f_i) 为到第 (i) 个商店为止,能够产生的最大值。当然如果扫荡区间超过了 (i) 也是没有关系的。我们考虑从第 (f_j) 个点转移,必须 (exists kquadleft[ i,j ight]subseteq left[ l_k,r_k ight])。
我们只要枚举能到的最远的点,再进行遍历转移。就能够在 (Theta(nm)) 的时间复杂度内完成。
答案:(f_n)。
(100;mathtt{pts})
这个优化实际上很好加。
注意到共有 (10^6) 个区间,但是如果区间 (left[l_i,r_i ight]) 和 (left[l_j,r_j ight]) 满足 (l_ile l_jle r_jle r_i) (即区间 (left[l_j,r_j ight]subseteqleft[l_i,r_i ight]))那么区间 (left[l_j,r_j ight]) 就没有用了。因为如果 (left[l_j,r_j ight]) 先,那么答案是不如 (left[l_i,r_i ight]) 先的,同时因为包含关系,所以后面再次扫荡 (left[l_j,r_j ight]) 答案就一定是 (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
", dp[n]);
return 0;
}
后记
这道题的难点就是在于想出 (f_i) 的状态定义。不要去试图使用哪个有点问题的贪心。
如果想不明白,可以试几组数据来手推一下,说不定就能想明白了。