【题解】购物
题目描述
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\) 的状态定义。不要去试图使用哪个有点问题的贪心。
如果想不明白,可以试几组数据来手推一下,说不定就能想明白了。
本文来自博客园,作者 5ab,转载请注明链接哦 qwq
博客迁移啦,来看看新博客吧 -> https://5ab-juruo.oier.space/