## [$AcWing$ $464$. 推销员](https://www.acwing.com/problem/content/description/466/) ### 一、理解测试用例 ```cpp {.line-numbers} s[i]: 1 2 3 4 5 a[i]: 1 2 3 4 5 ``` ① 取$1$个:$5*2+5=15$ ② 取$2$个:$(5+4)+2*5=19$ ③ 取$3$个:$(5+4+5)+2*5=22$ ④ 取$4$个:$(2+3+4+5)+2*5=24$ ⑤ 取$5$个:$(1+2+3+4+5)+2*5=25$ ### 二、解题思路 距离是真的烦人,那就先不管它吧。因为想要疲劳值最大,那么先 **按照疲劳值从大到小排序** 一遍。 此时,可以判断出,最大值 **可能** 为:对于每个$X$,只要加上前$X$大的疲劳值,再加上 **这些数中距离最远** 的并 **乘以$2$**,也就是:$$\sum_{i=1}^{X}(a[i])+s[j]∗2$$ 其中,$s[j]$表示前 $X$ 个距离最远的。 但是,推销员可以通过 **走远一点**,虽然疲劳值比前面小,但是有可能把路程一算,反而更大。 因此,可以把$a[]$,即疲劳值中,**前$X$大中的最小值,即第$X$大的那一家舍去**,看看能不能通过走更远来换取更大的疲劳值总和。 而从后面看,**一定也会存在一个最大值**,把这个最大值和前面$X−1$个疲劳值总和加起来,看看会不会比前面第一种的情况大。 #### 证明:只需舍去最小值来走更远,无需舍去更多数来走更远。 其实这也不是一个证明.. 因为已经从大到小排序了,所以,如果舍去$2$个疲劳值,那么后面只会在加上两个更小的疲劳值,以及两个之间最大的距离并乘$2$,那么这样还不如只舍去一个。 $2$个不行,那么$2$个以上更是不行了。 #### **举栗子** 硬讲太累了,还是用一个例子吧。 ```cpp {.line-numbers} s[i]:1,3,4,5,11 a[i]:5,4,2,1,1 ``` - 若$X==1$: - 直接取,值为:$1×2+5=7$ - 如果舍去当前(**最小**)的往后跑,值为:$11×2+1=23$ 所以,最大值就为$max(7,23)=23$。 - 若$X==2$: - 直接取,值为:$3×2+5+4=15$ - 把其中 **最小** 值舍去,也就是$4$去掉,往后跑值为:$11×2+1+5=28$,那么最大值为$max(15,28)=28$。 - 若把次小疲劳值,即$5$,也往后跑,那么值为$11×2+1=23$,可以发现,距离并未因此改变,仍为$11$,而位置移后,疲劳值只会减少,故只能移动最小疲劳值。 那么,最后一个问题,酱紫复杂度是会炸掉的,原因分析: 我们需要哪些信息呢? ① $sum[i]:a[i]$数组前$i$位的前缀和,比如上面的$5+4$ ② 除了计算疲劳值的前缀和,还需要一个前$i$个住房中,找到最大的距离值,这是题目中要求的数据,它能不能预处理出来呢?答案是可以的:从$i \sim n$一路$max$ $pk$不就记录下来了吗~ 我们用$g[i]$来记录这个信息。 ③ 把前$i$位中第$i$位去掉,保留前$i-1$位,那么,需要知道后面这一堆住房中距离的最大值。 很明显,这里在倒序由后向前进行$PK$记录即可。这里有一个坑点,就是如果你决策了选择某个住房,那么这个距离的最大值你肯定是妥妥的享用了,但不要忘记还要加这住房的疲劳值$a[i].b$ 这样子预处理,就可以实现$O(nlogn)$啦 $qaq$! **最终二选一就可以了~** ```cpp {.line-numbers} #include using namespace std; const int N = 100010; int n; // n个客户 int sum[N], g[N], h[N]; // 疲劳值前缀和 前i个最大值 后i个最大值 struct Node { int s; // 距离 int b; // 疲劳 const bool operator<(const Node &W) { return b > W.b; } } a[N]; int main() { cin >> n; for (int i = 1; i <= n; i++) cin >> a[i].s; // 住户i到入口的距离 for (int i = 1; i <= n; i++) cin >> a[i].b; // 向i住户推销产品会积累的疲劳值 sort(a + 1, a + 1 + n); // 按疲劳由大到小排序 for (int i = 1; i <= n; i++) sum[i] = sum[i - 1] + a[i].b; // 预算出疲劳值的前缀和 for (int i = 1; i <= n; i++) g[i] = max(g[i - 1], 2 * a[i].s); // 前i个距离最大值 for (int i = n; i >= 1; i--) h[i] = max(h[i + 1], 2 * a[i].s + a[i].b); // 后i个距离最大值 for (int i = 1; i <= n; i++) printf("%d\n", max(sum[i] + g[i], sum[i - 1] + h[i])); return 0; } ```