推销员 贪心 单调队列优化

推销员

题目描述

阿明是一名推销员,他奉命到螺丝街推销他们公司的产品。螺丝街是一条死胡同,出口与入口是同一个,街道的一侧是围墙,另一侧是住户。螺丝街一共有 N N N家住户,第ii家住户到入口的距离为 S i S_i Si米。由于同一栋房子里可以有多家住户,所以可能有多家住户与入口的距离相等。阿明会从入口进入,依次向螺丝街的 X X X家住户推销产品,然后再原路走出去。

阿明每走 1 1 1米就会积累 1 1 1点疲劳值,向第ii家住户推销产品会积累 A i A_i Ai点疲劳值。阿明是工作狂,他想知道,对于不同的 X X X,在不走多余的路的前提下,他最多可以积累多少点疲劳值。

输入格式

第一行有一个正整数 N N N,表示螺丝街住户的数量。
接下来的一行有 N N N个正整数,其中第 i i i个整数 S i S_i Si表示第 i i i家住户到入口的距离。数据保证 S 1 ≤ S 2 ≤ … ≤ S n < 1 0 8 S_1≤S_2≤…≤S_n<10^8 S1S2Sn<108
接下来的一行有 N N N个正整数,其中第 i i i个整数 A i A_i Ai表示向第 i i i户住户推销产品会积累的疲劳值。数据保证 A i < 1000 A_i<1000 Ai<1000

输出格式

输出 N N N行,每行一个正整数,第i行整数表示当 X = i X=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

说明/提示

样例一说明:
X = 1 X=1 X=1:向住户5推销,往返走路的疲劳值为5+5,推销的疲劳值为5,总疲劳值为15。
X = 2 X=2 X=2:向住户4、5推销,往返走路的疲劳值为5+5,推销的疲劳值为4+5,总疲劳值为5+5+4+5=19。
X = 3 X=3 X=3:向住户3、4、5推销,往返走路的疲劳值为5+5,推销的疲劳值3+4+5,总疲劳值为5+5+3+4+5=22。
X = 4 X=4 X=4:向住户2、3、4、5推销,往返走路的疲劳值为5+5,推销的疲劳值2+3+4+5,总疲劳值5+5+2+3+4+5=24。
X = 5 X=5 X=5:向住户1、2、3、4、5推销,往返走路的疲劳值为5+5,推销的疲劳值1+2+3+4+5,总疲劳值5+5+1+2+3+4+5=25。

样例二说明:
X = 1 X=1 X=1:向住户4推销,往返走路的疲劳值为4+4,推销的疲劳值为4,总疲劳值4+4+4=12。
X = 2 X=2 X=2:向住户1、4推销,往返走路的疲劳值为4+4,推销的疲劳值为5+4,总疲劳值4+4+5+4=17。
X = 3 X=3 X=3:向住户1、2、4推销,往返走路的疲劳值为4+4,推销的疲劳值为5+4+4,总疲劳值4+4+5+4+4=21。
X = 4 X=4 X=4:向住户1、2、3、4推销,往返走路的疲劳值为4+4,推销的疲劳值为5+4+3+4,总疲劳值4+4+5+4+3+4=24。或者向住户1、2、4、5推销,往返走路的疲劳值为5+5,推销的疲劳值为5+4+4+1,总疲劳值5+5+5+4+4+1=24。
X = 5 X=5 X=5:向住户1、2、3、4、5推销,往返走路的疲劳值为5+5,推销的疲劳值为5+4+3+4+1,总疲劳值5+5+5+4+3+4+1=27。

数据规模:

对于20%的数据, 1 ≤ N ≤ 20 1≤N≤20 1N20
对于40%的数据, 1 ≤ N ≤ 100 1≤N≤100 1N100
对于60%的数据, 1 ≤ N ≤ 1000 1≤N≤1000 1N1000
对于100%的数据, 1 ≤ N ≤ 100000 1≤N≤100000 1N100000


解题思路

题目大意:对于每一个 X X X,在序列中选出 X X X个数,使得 S x × 2 + ∑ i = 1 i ≤ X A i S_x \times 2 + \sum_{i=1}^{i\leq X}A_i Sx×2+i=1iXAi的值最大。
(越解释越复杂。。。)

由于 X X X的单调递增性,所以可以考虑 X X X每递增一次,用贪心策略维护一次答案。
先从 X = 1 X=1 X=1的情况入手:很显然,答案是 max ⁡ 1 ≤ i ≤ n ( S i × 2 + A i ) \max_{1\leq i \leq n}(S_i\times2+A_i) max1in(Si×2+Ai),枚举一遍就可以得到答案了。
紧接着,分两种情况讨论:

  1. 往巷子深处推销即向比居住在当前最远处人家住得更远的人家推销
  2. 给沿路的人家推销即向比居住在当前最远处人家住得更近的人家推销
    在这里插入图片描述

往巷子深处推销

设当前最深处人家在 p o s pos pos处,再设 s u m sum sum表示当前推销疲劳值,设 d i s dis dis表示当前路程疲劳值
p o s + 1 pos+1 pos+1开始枚举,算出巷子深处的路程最大疲劳值 m a x d i s = max ⁡ p o s + 1 ≤ j ≤ n ( A j × 2 ) maxdis=\max_{pos+1\leq j\leq n}(A_j\times2) maxdis=pos+1jnmax(Aj×2)且记录那一户人家 n e x t p o s nextpos nextpos
往巷子深处推销的总最大疲劳值即为 s u m 2 = m a x d i s + s u m + B n e x t p o s sum2=maxdis+sum+B_{nextpos} sum2=maxdis+sum+Bnextpos

给沿路的人家推销

1 1 1开始枚举,得出 p o s pos pos之前的人家的最大推销疲劳值,即 m a x n = max ⁡ 1 ≤ j < p o s B j maxn=\max_{1\leq j < pos}B_j maxn=1j<posmaxBj
给沿路人家推销的总最大疲劳值即为 s u m 1 = d i s + s u m + m a x n sum1=dis+sum+maxn sum1=dis+sum+maxn

前后对比

比较 s u m 1 sum1 sum1 s u m 2 sum2 sum2
如果 s u m 1 < s u m 2 sum1<sum2 sum1<sum2那么更新 s u m = s u m + m a x n sum=sum+maxn sum=sum+maxn
如果 s u m 1 > s u m 2 sum1>sum2 sum1>sum2那么更新 d i s = m a x d i s , s u m = s u m + B n e x t p o s , p o s = n e x t p o s dis=maxdis,sum=sum+B_{nextpos},pos=nextpos dis=maxdis,sum=sum+Bnextpos,pos=nextpos


更快的方法

由于在计算中出现了形如 max ⁡ l e f t ≤ i ≤ r i g h t X i \max_{left\leq i \leq right}X_i leftirightmaxXi
的式子,所以可以考虑用单调队列优化
总体思路到此结束。。。代码特别长。。。


代码

#include<iostream>
#include<cstdio>
#include<queue>

using namespace std;
int n,s[100005],a[100005],maxn,pos,ansdis,ans;
int main()
{
    
    
	priority_queue< int , vector<int > > q;//快速获取最大值 
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
	{
    
    
		scanf("%d",&s[i]);
		s[i]*=2;//来回路程 
	}
	for(int i=1;i<=n;i++)
		scanf("%d",&a[i]);
	for(int i=1;i<=n;i++)
	{
    
    
		if(maxn<s[i]+a[i])
		{
    
    
			maxn=s[i]+a[i];
			pos=i;
		}
	}
	printf("%d\n",maxn);//获取X=1的答案 
	ansdis=s[pos];ans=a[pos];//用于累加疲劳值,方便后面做贪心对比 
	for(int i=1;i<pos;i++)//当前最佳答案 前面的 最大疲劳值 
		q.push(a[i]);
	for(int i=2;i<=n;i++)
	{
    
    
		int tmp=q.top();int tmppos=0;
		maxn=0;
		for(int j=pos+1;j<=n;j++)
			if(s[j]+a[j]>maxn)
			{
    
    
				tmppos=j;
				maxn=s[j]+a[j];//记录当前最佳答案 后面的 最大疲劳值 
			} 
		if(ansdis+ans+tmp<maxn+ans)//前后对比,决定是往巷子里深处走
		{
    
    
			ansdis=s[tmppos];//累加的用处 
			ans+=a[tmppos];//累加的用处
			q.pop();
			for(int j=pos+1;j<tmppos;j++) q.push(a[j]);//记录当前最佳答案 前面的 最大值 
			pos=tmppos;//更新最佳答案 
		}
		else//或是给沿路人家推销 
		{
    
    
			ans+=tmp;//累加的用处 
			q.pop();//用过了就出队 
		}
		printf("%d\n",ans+ansdis);//输出答案 
	}
	return 0;
}

猜你喜欢

转载自blog.csdn.net/bell041030/article/details/103092141