首页 > 试题广场 >

健身

[编程题]健身
  • 热度指数:625 时间限制:C/C++ 1秒,其他语言2秒 空间限制:C/C++ 128M,其他语言256M
  • 算法知识视频讲解
因为公司有免费健身福利,快手程序员老铁们都很爱健身,而且他们健身时像工作一样充满效率。
他们把健身过程神奇的简化了出来:健身有N种锻炼方式可选,器材可看成在一条直线上。
每种锻炼方式距门口Xi米,因为有的器材上可以支持多种锻炼方式,因此有可能出现两种锻炼方式的距离是一样的,即Xa = Xb

老铁们一进健身房门口就开启健身形态,每走1米,就能获得1点锻炼效果值,而每种锻炼方式也有Ei的效果值,锻炼的过程就是从门口走到某种锻炼方式锻炼,然后到下一个方式锻炼,最后返回门口的过程。需要注意的是,锻炼过程中老铁们不会为了获得效果而刻意走回头路。

老铁们很想知道如果想选择某几种锻炼方式,怎样获得最大锻炼效果。


输入描述:
第一行N,表示锻炼方式的个数

第二行N个整数,表示每种锻炼方式距门口的距离

第三行N个整数,表示每种锻炼方式的效果值


输出描述:
N个整数,第k行表示选择k种锻炼方式时获得的最大锻炼效果
示例1

输入

5
1 2 3 4 5
1 1 1 1 1

输出

11
12
13
14
15

说明

对于样例第一行,老铁选择去第5种锻炼方式,考虑来回路程,一共获得5+1+5点锻炼效果值

备注:
对于所有数据,N <= 100,000,输出结果在32位整数范围内
思路:
ei是能量,dist是距离
要求解max(e1+e2+e3+…+en)+max(dist*2)
而dist和e1有关,
即求解max(e1,e2,e3+en-1)+max(en)+max(dist*2)
此时可看出,前n-1个一定是val的最大值,而max(en)+max(dist*2)可以看到要么是
1.en+dist*2的和的值最大,否则
2.val最大,dist是前面的某个n的值。
所以应该找出前k-1个最大的E,剩下的如果器材A的d比前面选的k-1个器材要大,那么最后一个器材就选A;否则,就选value第k大的器材。
python版本100%通过:
N=int(input())
distance = input().split()
exp = input().split()
 
max_to_choose=[]
for i in range(0,N):
    info={'dist':int(distance[i]),'exp':int(exp[i])}
    max_to_choose.append(info)
max_exp=sorted(max_to_choose,key=lambda info:info['exp'],reverse=True) # 按照健身效果排序,由于排序保证是稳定的。所以健身效果相同时,距离大的在后面

max_dist=0
kmax_save=0
kmax_exp=[]
save_index=0
for i in range(0,N):
    if(i>0):
        kmax_save=kmax_save+max_exp[i-1]['exp']
        kmax_exp.append(kmax_save)
        if(max_exp[i-1]['dist']>max_dist):
            max_dist=max_exp[i-1]['dist']
    else:
        kmax_exp.append(0)
    maxval=0
    value=0
    if(save_index<=i):
        for k in range(i,N):
            value=max_exp[k]['exp']+max_exp[k]['dist']*2
            if(value>maxval):
                maxval=value
                save_index=k
    # 从剩下的里找出2d+V最大的的器材A
    if(max_dist<max_exp[save_index]['dist']):
        kmax_exp[i]=kmax_exp[i]+2*max_exp[save_index]['dist']+max_exp[save_index]['exp']
    else:
        kmax_exp[i]=kmax_exp[i]+2*max_dist+max_exp[i]['exp']
   
for km in kmax_exp:
    print(km)


c语言版本:80%通过
#include <stdio.h>
#include <stdlib.h>
void quick_sort(int s,int k,int a[][2])
{
    if(s<k)
    {
        int i=s,j=k;
        int tmp=a[i][0];
        int tmp2=a[i][1];
        while(i<j)
        {

            while(i<j && a[j][0]<tmp)
            {
                j--;
            }
            if(i<j)
            {
                a[i][0]=a[j][0];
                a[i][1]=a[j][1];
                i++;
            }

            while(i<j && a[i][0]>=tmp)
            {
                i++;
            }
            if(i<j)
            {
                a[j][0]=a[i][0];
                a[j][1]=a[i][1];
                j--;
            }
        }
        a[i][0]=tmp;
        a[i][1]=tmp2;
        quick_sort(s,i-1,a);
        quick_sort(i+1,k,a);
    }
}

void print(int a[][2],int p,int e)
{
    int i=0;
    for(i=p; i<=e; i++)
    {
        printf("exp:%d dist:%d\n",a[i][0],a[i][1]);
    }
    printf("\n");
}

void print_re(int kmax_exp[])
{
    int i=0;
    while(kmax_exp[i])
    {
        printf("%d\n",kmax_exp[i]);
        i++;
    }
}
int info[100000][2]= {0};
int kmax_exp[100000]= {0};
int choosed=0;

int main()
{
    int i=0,n=0;
    scanf("%d",&n);
    for(i=0; i<n; i++)
    {
        scanf("%d",&info[i][1]);
    }
    for(i=0; i<n; i++)
    {
        scanf("%d",&info[i][0]);
    }
    quick_sort(0,n-1,info); //复杂度n2
    //print(info,0,n-1);
    int k=0;
    int max_dist=0;
    int kmax_save=0;

    int save_index=-1;
    for(i=0; i<n; i++)
    {
        //先选k-1个value最大的器材
        if(i>0) //由于前k-1个始终相同,直接保存结果
        {
            kmax_save=kmax_save+info[i-1][0];
            kmax_exp[i]=kmax_save;
            choosed=i-1; //标记前i-1个为已经选择
            if(info[i-1][1]>max_dist)
            {
                max_dist=info[i-1][1];//保存最大距离
            }
        }
        //for(k=0;k<n;k++){
        //    printf("%d ",choosed[k]);
        //}
        //printf("\n");
        //print_re(kmax_exp);
        int exp=info[i][0]; //保存第k大的经验值
        int maxval=0;
        int value=0;
        //只有当en+dist*2最大的值被作为前面的
        //选择了之后才需要重新选择。从第k个开始找。
        if(save_index<=i)
        {
            for(k=i; k<n; k++)
            {
                //从剩下n-i个里面的选择一个en+dist*2最大的
                value=info[k][0]+info[k][1]*2;
                if(value>maxval)
                {
                    maxval=value;
                    save_index=k;//保存选择的这个剩下最大的器材的索引
                }
            }
        }
        //printf("maxdist:%d maxval: %d save_index:%d \n",max_dist,maxval,save_index);
        if(max_dist<info[save_index][1])
        {
            kmax_exp[i]=kmax_exp[i]+2*info[save_index][1]+info[save_index][0];
        }
        else
        {
            kmax_exp[i]=kmax_exp[i]+2*max_dist+exp;
        }
    }
    print_re(kmax_exp);
    return 0;
}


编辑于 2020-04-16 02:11:57 回复(4)

声明:通过这道题的几份代码都是错的,能AC可能是因为测试数据设计不合理。以下是我的3份代码,欢迎大家一起讨论。

# coding:utf-8
import sys
import heapq
if __name__ == '__main__':
    n = int(sys.stdin.readline().strip())
    distance_list = map(int, str(sys.stdin.readline().strip()).split())
    val_list = map(int, str(sys.stdin.readline().strip()).split())
    distance_val_list = []
    for i in range(n):
        distance_val_list.append((distance_list[i], val_list[i]))
    distance_val_list.sort(key=lambda x: x[0])

    # 可以证明选择k+1种锻炼方式可由选择k种锻炼方式扩展一个节点生成,维护两个堆,时间复杂度为O(nlogn),得分100
    left_heap = []
    right_heap = []
    farthest_index = -1
    chosen_set = set()
    for i in range(n):
        heapq.heappush(
            right_heap, (-(2 * distance_val_list[i][0] + distance_val_list[i][1]), i))
        heapq.heappush(
            left_heap, (-distance_val_list[i][1], i))
    ans = 0
    while left_heap or right_heap:
        right_val = 0
        right_index = -1
        left_val = 0
        left_index = -1
        while right_heap and (right_heap[0][1] in chosen_set or right_heap[0][1] < farthest_index):
            heapq.heappop(right_heap)
        if right_heap:
            right_val, right_index = -right_heap[0][0], right_heap[0][1]
            if farthest_index >= 0:
                right_val -= 2 * distance_val_list[farthest_index][0]
        while left_heap and left_heap[0][1] in chosen_set:
            heapq.heappop(left_heap)
        if left_heap:
            left_val, left_index = -left_heap[0][0], left_heap[0][1]
        if not (left_heap or right_heap):
            break
        if left_val > right_val:
            heapq.heappop(left_heap)
            chosen_set.add(left_index)
            ans += left_val
        else:
            heapq.heappop(right_heap)
            chosen_set.add(right_index)
            farthest_index = right_index
            ans += right_val
        print ans

    # 可以证明选择k+1种锻炼方式可由选择k种锻炼方式扩展一个节点生成,线性查找,时间复杂度为O(n^2),得分60
    farthest_index = -1
    count = 0
    chosen_set = set()
    ans = 0
    while count < n:
        max_val = 0
        index = -1
        for i in range(n):
            if i not in chosen_set:
                if i < farthest_index:
                    val = distance_val_list[i][1]
                elif i > farthest_index:
                    if farthest_index < 0:
                        val = 2 * \
                            distance_val_list[i][0] + distance_val_list[i][1]
                    else:
                        val = 2 * \
                            (distance_val_list[i][0] - distance_val_list[farthest_index]
                             [0]) + distance_val_list[i][1]
                else:
                    assert False
                if val > max_val:
                    max_val = val
                    index = i
        chosen_set.add(index)
        if index > farthest_index:
            farthest_index = index
        ans += max_val
        print ans
        count += 1

    # 没有用上边两种方法用到的结论,直接暴力dp(实际上可以用一维数组,懒得写了),时间复杂度为O(n^3),得分40
    dp = [[] for i in range(n)]
    for j in range(n):
        dp[0].append(2 * distance_val_list[j][0] + distance_val_list[j][1])
    for i in range(1, n):
        for j in range(0, n):
            if i > j:
                dp[i].append(0)
            else:
                max_val = 0
                for k in range(0, j):
                    max_val = max(
                        max_val, dp[i - 1][k] + 2 * (distance_val_list[j][0] - distance_val_list[k][0]))
                dp[i].append(max_val + distance_val_list[j][1])
    for i in range(n):
        print max(dp[i])

编辑于 2020-03-22 15:09:17 回复(2)
发表于 2020-04-29 14:07:56 回复(0)
#include<bits/stdc++.h>
using namespace std;
struct Node{
    int id;
    int dis;
    int ex;
};
//重载运算符,按其和作为优先指标,注意'<'表示大顶堆
struct cmpSum{
    bool operator()(Node a,Node b){
        return 2 * a.dis + a.ex < 2 * b.dis + b.ex;
    }
};
//以其ex值作为优先指标
struct cmpEx{
    bool operator()(Node a, Node b){
        return a.ex < b.ex;
    }
};
int main(void){
    int N;
    cin >> N;
    vector<int> dis(N+1,0),ex(N+1,0);
    int temp;
    for (int i = 1; i <= N; i++){
        cin >> temp;
        dis[i] = temp;
    }
    for (int i = 1; i <= N; i++){
        cin >> temp;
        ex[i] = temp;
    }
    //两个优先队列
    priority_queue<Node,vector<Node>,cmpSum> queueSum;
    priority_queue<Node,vector<Node>,cmpEx> queueEx;
    //O(2nlogn)
    for(int i = 1; i <= N; i++){
        queueSum.push(Node{i,dis[i],ex[i]});
        queueEx.push(Node{i,dis[i],ex[i]});
    }
    int ans = 0, newMax = 0, preFar = 0;
    int newIndex = 0;
    int cur1, cur2;
    //记录用过的结点
    vector<int> visited(N + 1, 0);
    //可选择i种器材,O(N)
    for (int i = 1; i <= N; i++){
        //懒更新,某个器材用过了不会立刻弹出队列,仅在visited数组中记录
        //如果它在队首,才弹出
        while(!queueSum.empty() && visited[queueSum.top().id]){
            queueSum.pop();
        }
        while(!queueEx.empty() && visited[queueEx.top().id]){
            queueEx.pop();
        }
        //查看两个队列的队首,新加入的器材必定在其中一个队列的队首
        //新加入的器材有可能比之前最远的器材远,也可能近
        //其中一部分贡献是自的ex值
        //如果更远的话,那还有一部分贡献是比之前最远多出来的部分的两倍
        //找出贡献值最大的那个器材的下标
        cur1 = 2 * max(0,(queueSum.top().dis - preFar)) + queueSum.top().ex;
        cur2 = 2 * max(0,(queueEx.top().dis - preFar)) + queueEx.top().ex;
        if (max(cur1, cur2) > newMax){
            newMax = max(cur1,cur2);
            //选择后要记录id,用于之后的访问标记与更新最远距离
            if (cur1 >= cur2) newIndex = queueSum.top().id;
            else newIndex = queueEx.top().id;
        }
        //从i-1种器材到i种,就是原答案基础上加上新的贡献
        ans += newMax;
        //记录当前最远的器材
        preFar = max(preFar, dis[newIndex]);
        //加上访问标记
        visited[newIndex] = 1;
        newMax = 0;
        cout << ans << endl;
    }
}

发表于 2022-08-30 18:03:00 回复(0)
N=int(input())
distance = list(map(int,input().split()))
exp = list(map(int,input().split()))
 
pure=sorted(enumerate(exp),key=lambda x:x[1],reverse=True) #只有自己锻炼效果的排序
#加上走路距离的效果排序
comp=sorted(enumerate([exp[i]+distance[i]*2 for i in range(N)]),key=lambda x:x[1],reverse=True)

currentMaxIndex=-1  #记录前i个锻炼效果最大值的最大下标
currentMaxExp=0  #记录前i个锻炼效果最大值的总和

for i in range(N):
    j=0
    if currentMaxIndex==N-1:
        #节省了下面while的很多次比较
        currentMaxExp+=pure[i][1]
        print(currentMaxExp+2*distance[currentMaxIndex])
        continue
        
    while comp[j][0]<=currentMaxIndex:
        j+=1
    tmp0=currentMaxExp+comp[j][1] #tmp0记录从前i-1个最大值的后面选取一个运动的效果最大值
    if currentMaxIndex>0:
        #tmp1记录从当前的最大下标之前选取一个运动的效果最大值
        tmp1=currentMaxExp+pure[i][1]+2*distance[currentMaxIndex]
    else:
        tmp1=0
    print(max(tmp0,tmp1))
    currentMaxExp+=pure[i][1]
    currentMaxIndex=max(currentMaxIndex,pure[i][0])
思路和题解里其他人是类似的。
发表于 2021-03-21 09:35:16 回复(0)
个人认为答案是对的 但是超时了 有大佬可以帮忙看一下吗
思路:对于选k个建身器材,先选k-1个value最大的器材,再从剩下的里面找出(2d+value)最大的器材A。如果器材A的d比前面选的k-1个器材要大,那么最后一个器材就选A;否则,就选value第k大的器材。(因为只有一个器材会影响最终练习效果的关于距离的部分)这样应该可以保持训练效率最大化。
#include<iostream>
#include<algorithm>
using namespace std;

class tool {
public:
    int d;
    int value;
    tool() {}
    tool(int a, int b) {
        d = a;
        value = b;
    }
    int mix() {
        return d * 2 + value;
    }
};

bool cmp(tool a, tool b) {
    return a.value > b.value;
}

int main() {
    int num;
    cin >> num;
    tool* ks = new tool[num];
    for (int i = 0; i < num; i++) {
        int a;
        cin >> a;
        ks[i].d = a;
    }
    for (int i = 0; i < num; i++) {
        int a;
        cin >> a;
        ks[i].value = a;
    }
    for (int k = 1; k < num + 1; k++) {
        int* choose = new int[num];
        for (int i = 0; i < num; i++) choose[i] = 0;
        sort(ks, ks + num, cmp);
        //前k-1个器材
        for (int i = 0; i < k - 1; i++) choose[i] = 1;
        int max = 0, index;
        //剩余器材mix最大的为max,序号为index
        for (int i = k - 1; i < num; i++) {
            if (ks[i].mix() > max) {
                max = ks[i].mix();
                index = i;
            }
        }
        int maxd = 0;
        //前k-1个距离最大为maxd
        for (int i = 0; i < k - 1; i++) {
            if (ks[i].d > maxd) maxd = ks[i].d;
        }
        if (ks[index].d > maxd)choose[index] = 1;
        else choose[k - 1] = 1;
        int total = 0;
        for (int i = 0; i < num; i++) {
            if (choose[i] == 1) total += ks[i].value;
            if (choose[i] == 1 && ks[i].d > maxd) maxd = ks[i].d;
        }
        total += 2 * maxd;
        cout << total << endl;
    }
    return 0;
}

编辑于 2020-03-27 15:46:20 回复(0)