P2672 推销员(贪心+线段树)

题目描述

阿明是一名推销员,他奉命到螺丝街推销他们公司的产品。螺丝街是一条死胡同,出口与入口是同一个,街道的一侧是围墙,另一侧是住户。螺丝街一共有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户人家,再找一户人家。这就需要分类讨论了:

  1. 这户人家的位置s[i]小于等于s[id],那么newAns1 = ans + max(s[i]小于等于s[id]的所有a[i]) = ans + max(a[1-id]); (因为题中给出的s[]是单增的)
  2. 这户人家的位置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];
  3. 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;
}

猜你喜欢

转载自blog.csdn.net/li_wen_zhuo/article/details/111464920