题解:P2672 [NOIP 2015 普及组] 推销员
题目传送门
是道很好的题
代码实现难度很低很低
但是基础的思维量还是能保证的
但是建议调绿
十五分钟就写完了
关键词:贪心、前后缀
简化题意
给定两个长度为N的序列:
\(S_i\):表示第i家住户到入口的距离,保证\(S_1 \leq S_2 \leq \cdots \leq S_N < 10^8\)
\(A_i\):表示向第i家住户推销产品获得的疲劳值,保证\(A_i < 1000\)
对于每个\(k \in [1,N]\),我们需要选择一个包含k家住户的集合\(K\),使得总疲劳值最大。
总疲劳值的计算公式为:
其中:
\(\sum_{i \in K} A_i\)表示推销k家住户的总疲劳值
\(2 \times \max_{i \in K} S_i\)表示往返最远住户的路程疲劳值
简化思考与解题思路
核心观察
$\ \ $ 疲劳值构成分析:
$\ \ $ $\ \ $ 总疲劳值由两部分组成:推销疲劳值之和与路程疲劳值
$\ \ $ $\ \ $ 路程疲劳值仅取决于最远访问的住户,与访问的其他住户距离无关
贪心策略:
对于给定的k,最优解有两种可能:
$\ \ $ a) 选择前k个最大的\(A_i\),加上这些住户中最远的\(S_i \times 2\)
$\ \ $ b) 选择前k-1个最大的\(A_i\),再加上一个距离更远的住户(即使其\(A_i\)较小)
数学表达: 对于每个k,最优解为:
其中:
\(pre[k]\):前k个最大\(A_i\)的和
\(max\_dis[k]\):前k个选择中的最大\(S_i\)
\(suf[k+1]\):从k+1开始最大的\((2S_i + A_i)\)值
算法实现要点
预处理步骤:
将住户按\(A_i\)降序排序,计算前缀和\(\textstyle {pre}\)
按原始\(S_i\)顺序计算后缀最大值\(\textstyle{suf}\),存储\((2S_i + A_i)\)
动态维护:
在遍历排序后的数组时,维护当前选择中的最大距离\(\textstyle{max\_dis}\)
对于每个k,比较两种策略取最大值
复杂度分析:
排序:\(O(N \log N)\)
预处理:\(O(N)\)
查询:\(O(N)\)
总复杂度:\(O(N \log N)\),完全满足题目数据范围要求
我们上CODE
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
int n, ans[N], now, suf[N], nid, pre[N];
struct node {
int c, x, id;
} a[N];
bool cmp(node a, node b)
{
return a.c > b.c;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr);
cout.tie(nullptr);
cin >> n;
for (int i = 1; i <= n; i++)
cin >> a[i].x;
for (int i = 1; i <= n; i++) {
cin >> a[i].c;
a[i].id = i;
}
for (int i = n; i >= 1; i--)
suf[i] = max(suf[i + 1], 2 * a[i].x + a[i].c); // 默认按照距离升序,处理贡献后缀
sort(a + 1, a + 1 + n, cmp); // 换成权值降序
for (int i = 1; i <= n; i++)
pre[i] = pre[i - 1] + a[i].c; // 处理前缀权值
for (int i = 1; i <= n; i++) {
if (a[i].x > now) {
now = a[i].x;
nid = a[i].id;
}
cout << max(pre[i] + 2 * now, pre[i - 1] + suf[nid + 1]) << endl;
}
return 0;
}
$\ $
文章由LLM润色
但是本人作出绝大部分贡献
LLM仅应用于排版和语文方面

浙公网安备 33010602011771号