首页 > 试题广场 >

[NOIP2015]推销员

[编程题][NOIP2015]推销员
  • 热度指数:708 时间限制:C/C++ 1秒,其他语言2秒 空间限制:C/C++ 256M,其他语言512M
  • 算法知识视频讲解

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

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


输入描述:

第一行有一个正整数N,表示螺丝街住户的数量。

接下来的一行有N个正整数,其中第i个整数Si表示第i家住户到入口的距离。数据保证S1≤S2≤…≤Sn<108

接下来的一行有N个正整数,其中第i个整数Ai表示向第i户住户推销产品会积累的疲劳值。数据保证Ai<103



输出描述:
输出N行,每行一个正整数,第i行整数表示当X=i时,阿明最多积累的疲劳值。
示例1

输入

5
1 2 3 4 5
1 2 3 4 5

输出

15
19
22
24
25

说明

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

输入

5
1 2 2 4 5
5 4 3 4 1

输出

12
17
21
24
27

说明

X=1:向住户4推销,往返走路的疲劳值为4+4,推销的疲劳值为4,总疲劳值4+4+4=12。
X=2:向住户1、4推销,往返走路的疲劳值为4+4,推销的疲劳值为5+4,总疲劳值4+4+5+4=17。
X=3:向住户1、2、4推销,往返走路的疲劳值为4+4,推销的疲劳值为5+4+4,总疲劳值4+4+5+4+4=21。
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:向住户1、2、3、4、5推销,往返走路的疲劳值为5+5,推销的疲劳值为5+4+3+4+1,总疲劳值5+5+5+4+3+4+1=27。

备注:
对于20%的数据,1≤N≤20;
对于40%的数据,1≤N≤100;
对于60%的数据,1≤N≤1000;
对于100%的数据,1≤N≤100000。
20秋招快手“健身房”,一个不依赖Si和Ai取值范围的解法

n = int(input())
poi = [int(x) for x in input().split()]
val = [int(x) for x in input().split()]
# 最优策略:要找n户
# 1. 找到最大+最右(+编号最大)的n户,设为集合S。“折返点”不可能在“集合S最右边的点”的左侧。因为左侧说明poi*2不如“最右”大,大小又不如最大的n户大。
# 2. “折返点”可能在右侧,因此在坐标[集合S最右边的点, +inf]的点中,找到val+poi*2最大的点作为“折返点”
# 3. 检查折返点是不是S中的点,不是的话,只能取S中的前n-1大的点+折返点
points = [(i, poi[i], val[i]) for i in range(n)]

points_by_poi = sorted(points, key=lambda x: x[1])
get_return_point = {} # 闭区间,max val+poi*2 in [key, end]
cur_max_point = (0, 0, float('-inf'))
for i in reversed(range(n)):
    p = points_by_poi[i]
    update = False
    if (p[2] + p[1] * 2 > cur_max_point[2] + cur_max_point[1] * 2 
        or p[2] + p[1] * 2 == cur_max_point[2] + cur_max_point[1] * 2 and p[0] < cur_max_point[0]):
        update = True
    if update:
        cur_max_point = p
    get_return_point[p[1]] = cur_max_point

points_by_val = sorted(points, key=lambda x: (x[2], x[1], x[0]), reverse=True)
presum = [points_by_val[0][2]]
for i in range(1, n):
    presum.append(presum[-1] + points_by_val[i][2])
cur_max = 0
rightest = []
for p in points_by_val:
    cur_max = max(cur_max, p[1])
    rightest.append(cur_max)

for i in range(n):
    # search return point in [start, end]
    start = rightest[i]
    return_point = get_return_point[start]
    if (return_point[1] == start 
        and (points_by_poi[i][1] < start
             or points_by_poi[i][1] == start and points_by_poi[i][0] <= return_point[0])): # 折返点在集合S中
        print(presum[i] + 2 * start)
    else:
        print(presum[i] - points_by_val[i][2] + return_point[2] + return_point[1] * 2)


发表于 2019-09-03 10:18:36 回复(0)
#dp一波,超时了。。。。
#include<stdio.h>
#include<iostream>
using namespace std;
int a[100005];
int b[100005];
int dp[100005];
int tmp[2][100005];
int main(){
    int n;
    cin>>n;
    for(int i=1;i<=n;i++){
        scanf("%d",&a[i]);
    }
    for(int i=1;i<=n;i++){
        scanf("%d",&b[i]);
    }
    int Max=0;
    for(int i=1;i<=n;i++){
        tmp[1][i]=b[i];
        dp[i]=a[i]*2+b[i];
        if(dp[i]>Max) Max=dp[i];
        if(tmp[1][i-1]>tmp[1][i])
            tmp[1][i]=tmp[1][i-1];
    }
    printf("%d\n",Max);
    for(int k=2;k<=n;k++){
        int Max=0;
        for(int j=k;j<=n;j++){
            dp[j]=a[j]*2+b[j]+tmp[(k-1)%2][j-1];
            tmp[k%2][j]=tmp[(k-1)%2][j-1]+b[j];
            if(Max<dp[j]) Max=dp[j];
            if(tmp[k%2][j-1]>tmp[k%2][j])
            tmp[k%2][j]=tmp[k%2][j-1];
        }
        printf("%d\n",Max);
    }
}
发表于 2019-08-26 14:50:05 回复(0)
方法就是把疲劳值从小到大排个序,然后从尾部开始一个一个取,当选到第i(i >= 2)个时有2种取法:一是取,那么X = i的答案就是[n-i+1,n]区间的疲劳值求和并加上其中最大距离的2倍;二是不取,那么答案便是[n-i+2,n]区间的疲劳值求和并加上[1,n-i]区间中(疲劳值+距离的2倍)最大的一个,为什么一定是前面那个最大?因为如果最大距离在[n-i+2,n]中,那显然是选取n-i的地方更增加疲劳值。2种取法取个最大即可。
最大的疑问是,为什么X=i时的最优解一定是建立在[n-i+2,n]区间中的数全部取完之后呢?
证明如下:
当i = 2时,第n个数是必取的,可以用反证法证明:假设取第x,y(x<y<n)个数时疲劳值最大,于是可以将其中一个不对距离贡献疲劳值的数换成第n个数,这样,无论最大距离是否更新,都出现了一个更优的解,与假设矛盾。
当i > 2时,也可以用反证法证明假设在[n-i+2,n]中取m(m<i)个数,在[1,n-i+1]中取i-m个数的组合才是最优的。可以分2种情况:(1)最大距离在[1,n-i+1]区间中,那么[1,n-i+1]区间非最大距离的数都可以用[n-i+2,n]中的数代替,显然可以有更优的解。(2)最大距离在[n-i+2,n]区间中,显然这i个数全部选后i个数是最优的,因为前面的数不对距离有所贡献。综上,与假设矛盾。
#include <bits/stdc++.h>
using namespace std;

#define rep(i,n) for (int i = 0; i < (n); ++i)
#define For(i,s,t) for (int i = (s); i <= (t); ++i)
#define rFor(i,t,s) for (int i = (t); i >= (s); --i)
#define foreach(i,c) for (__typeof(c.begin()) i = c.begin(); i != c.end(); ++i)
#define rforeach(i,c) for (__typeof(c.rbegin()) i = c.rbegin(); i != c.rend(); ++i)

#define pr(x) cout << #x << " = " << x << "  "
#define prln(x) cout << #x << " = " << x << endl

#define ALL(x) x.begin(),x.end()
#define INS(x) inserter(x,x.begin())

#define ms0(a) memset(a,0,sizeof(a))
#define msI(a) memset(a,inf,sizeof(a))

#define pii pair<int,int> 
#define piii pair<pair<int,int>,int> 
#define mp make_pair
#define pb push_back
#define fi first
#define se second

inline int gc(){
    static const int BUF = 1e7;
    static char buf[BUF], *bg = buf + BUF, *ed = bg;
    
    if(bg == ed) fread(bg = buf, 1, BUF, stdin);
    return *bg++;
} 

inline int ri(){
    int x = 0, f = 1, c = gc();
    for(; c<48||c>57; f = c=='-'?-1:f, c=gc());
    for(; c>47&&c<58; x = x*10 + c - 48, c=gc());
    return x*f;
}

typedef long long LL;
const int maxN = 1e5 + 7;


int n; 
int dist[maxN], cost[maxN];
int suffixMax[maxN]; //记录dist的后缀最大值 
int f[maxN]; // f[i]表示当X=1时在1~i范围内只选择1家住户的最优解 
int prefixSum[maxN]; // cost的前缀和 

int getSum(int x, int y){
    return x < y ? prefixSum[y] - prefixSum[x] : 0;
}

void mergeSort(int l, int r){
    if(l >= r) return;
    int mid = (l+r) >> 1;
    
    mergeSort(l, mid);
    mergeSort(mid+1, r);
    
    int tmp1[r-l+1], tmp2[r-l+1];
    int i = l, j = mid + 1, k = 0;
    
    while(i <= mid && j <= r){
        if(cost[i] > cost[j] || cost[i] == cost[j] && dist[i] > dist[j]){
            tmp1[k] = cost[j];
            tmp2[k++] = dist[j++];
        }
        else{
            tmp1[k] = cost[i];
            tmp2[k++] = dist[i++];
        }
    }
    while(i <= mid){
        tmp1[k] = cost[i];
        tmp2[k++] = dist[i];
        ++i;
    }
    while(j <= r){
        tmp1[k] = cost[j];
        tmp2[k++] = dist[j];
        ++j;
    }
    
    rep(i, k){
        cost[i+l] = tmp1[i];
        dist[i+l] = tmp2[i];
    }
}

int main(){
    scanf("%d", &n);
    For(i, 1, n) dist[i] = ri();
    For(i, 1, n) cost[i] = ri();
    
    mergeSort(1, n);
    rFor(i, n, 1) suffixMax[i] = max(suffixMax[i+1], dist[i]);
    
    For(i, 1, n) f[i] = max(f[i-1], cost[i] + dist[i]*2);
    
    For(i, 1, n) prefixSum[i] = prefixSum[i-1] + cost[i];
    
    rFor(i, n, 1) printf("%d\n", max(getSum(i, n) + f[i], getSum(i-1, n) + 2*suffixMax[i]));
    return 0;
}

编辑于 2019-04-06 22:20:01 回复(0)