题目描述
阿明是一名推销员,他奉命到螺丝街推销他们公司的产品。螺丝街是一条死胡同,出口与入口是同一个,街道的一侧是围墙,另一侧是住户。螺丝街一共有N家住户,第i家住户到入口的距离为Si米。由于同一栋房子里可以有多家住户,所以可能有多家住户与入口的距离相等。阿明会从入口进入,依次向螺丝街的X家住户推销产品,然后再原路走出去。阿明每走1米就会积累1点疲劳值,向第ii家住户推销产品会积累Ai点疲劳值。阿明是工作狂,他想知道,对于不同的X,在不走多余的路的前提下,他最多可以积累多少点疲劳值。
输入格式
第一行有一个正整数N,表示螺丝街住户的数量。接下来的一行有N个正整数,其中第i个整数Si表示第ii家住户到入口的距离。数据保证S1≤S2≤…≤Sn<10^8。
接下来的一行有N个正整数,其中第i个整数Ai表示向第i户住户推销产品会积累的疲劳值。数据保证Ai<1000。
输出格式
输出N行,每行一个正整数,第i行整数表示当X=i时,阿明最多积累的疲劳值。
样例
输入 #1
5
1 2 3 4 5
1 2 3 4 5
输出 #1
15
19
22
24
25
输入 #2
5
1 2 2 4 5
5 4 3 4 1
输出 #2
12
17
21
24
27
数据说明
对于20%的数据,1≤N≤20;
对于40%的数据,1≤N≤100;
对于60%的数据,1≤N≤1000;
对于100%的数据,1≤N≤100000。
题目分析
首先,这个题最重要的贪心策略是:推销x户人家积累的最大疲惫值,一定是基于推销x-1户人家积累的最大疲惫值的。这个贪心结论我就不证明了(我当时是猜出来的,没证明(其实也不会证))。
有了这个结论之后,我们就可以考虑这道题该怎么做了。
假设我们已经求出了推销x-1户人家积累的最大疲惫值ans,且这x-1户人家中,距离最远的一户的位置是s[id]。
那么如果要求出推销x户人家积累的最大疲惫值newAns,我们就要基于前面的x-1户人家,再找一户人家。这就需要分类讨论了:
- 这户人家的位置s[i]小于等于s[id],那么
newAns1 = ans + max(s[i]小于等于s[id]的所有a[i]) = ans + max(a[1-id]); (因为题中给出的s[]是单增的)
- 这户人家的位置s[i]大于s[id],那么
newAns2 = ans + max(s[i]大于s[id]的所有 a[i]+2*s[i])-2*s[id] = ans + max(2*s[(id+1)-n]+a[(id+1)-n]) - 2*s[id];
newAns=max(newAns1,newAns2);
然后问题就转化为了:如何快速的求出一段序列中 a[i] 和 a[i]+2*s[i] 的最大值。这个问题可以通过线段树解决。
代码如下
#include <iostream>
#include <cstdio>
#include <cmath>
#include <string>
#include <cstring>
#include <map>
#include <queue>
#include <vector>
#include <set>
#include <algorithm>
#define LL long long
#define PII pair<int,int>
#define x first
#define y second
using namespace std;
const int N=1e5+5,INF=0x3f3f3f3f;
struct Node{ //线段树
int l,r;
PII maxs,maxa; //因为需要最远人家的编号id,所以maxs和maxa要用pair类型,来存最大值及其位置
}tr[N*4];
int a[N],s[N];
void pushup(int u)
{
tr[u].maxs=max(tr[u<<1].maxs,tr[u<<1|1].maxs);
tr[u].maxa=max(tr[u<<1].maxa,tr[u<<1|1].maxa);
}
void build(int u,int l,int r) //建树
{
if(l==r) tr[u]={l,r,{2*s[l]+a[l],l},{a[l],l}};
else {
tr[u]={l,r};
int mid=l+r>>1;
build(u<<1,l,mid),build(u<<1|1,mid+1,r);
pushup(u);
}
}
void remove(int u,int x) //删除x位置上的节点
{ //因为维护的信息都是最大值,因此删除一个节点只需要将该位置的数变为0即可
if(tr[u].l==x&&tr[u].r==x) tr[u]={x,x,{0,0},{0,0}};
else {
int mid=tr[u].l+tr[u].r>>1; //二分查找x位置
if(mid>=x) remove(u<<1,x);
else remove(u<<1|1,x);
pushup(u);
}
}
PII query(int u,int l,int r,bool st) //查找[l,r]区间中的最大值
{ //st=1时,查找的是a[i]的最大值;st=0时,查找的是a[i]+2*s[i]的最大值
if(l<=tr[u].l&&tr[u].r<=r)
{
if(st) return tr[u].maxa;
else return tr[u].maxs;
}
int mid=tr[u].l+tr[u].r>>1;
PII ans={0,0};
if(mid>=l) ans=query(u<<1,l,r,st);
if(mid<r) ans=max(ans,query(u<<1|1,l,r,st));
return ans;
}
int main()
{
int n;
scanf("%d",&n);
for(int i=1;i<=n;i++) scanf("%d",&s[i]);
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
build(1,1,n);
int id=tr[1].maxs.y; //第一户的最大值直接就是tr[1].maxs
int ans=tr[1].maxs.x; //记录答案ans和最远位置id
printf("%d\n",ans);
remove(1,id); //每次找到一户人家后要记得删除该节点
for(int i=1;i<n;i++)
{
PII t1=query(1,1,max(1,id-1),1); //第一种情况的解
PII t2=query(1,min(n,id+1),n,0); t2.x-=2*s[id]; //第二种情况的解
if(t1>t2) //两者取最大值
{
remove(1,t1.y); //删除该位置的数
ans+=t1.x; //更新ans,因为最远位置还是s[id],因此不需要更新id
printf("%d\n",ans);
}
else {
remove(1,t2.y);
ans+=t2.x; //更新ans
printf("%d\n",ans);
id=t2.y; //更新id
}
}
return 0;
}