#P1014. 第1题-塔子玩游戏
          
                        
                                    
                      
        
              - 
          
          
                      1000ms
            
          
                      Tried: 619
            Accepted: 190
            Difficulty: 4
            
          
          
          
                       所属公司 : 
                              美团
                                
            
                        
              时间 :2022年10月8日
                              
                      
          
 
- 
                        算法标签>贪心算法          
 
第1题-塔子玩游戏
思路
首先将题目转化为塔子完成整个关卡最少损失多少生命值,这时每个回合用不用塔子起飞所造成的贡献相对独立。
考虑第 i 个回合用塔子起飞,会少损失 vi 点体力,并在之后的 n−i 个关卡中每关增加 1 点恢复,但是会少增加cnti点体力。 这里的 cnti 代表之前用过的塔子起飞次数。
所以选择第i回合所带来的增益就是 vi+n−i−cnti−1 。由于每个位置的选择是独立的(也就是说你选第1个和你选第2个是没关系的。没规定你选了第1个必须选第2个。也没规定你选了第1个就不能选第2个)。所以我们就是要求:
$$\Large max\ \sum_{i \in \{i_1,i_2,...,i_x\}}^{} v_i+n-i-cnt_{i-1} \\ \Large = \sum_{i \in \{i_1,i_2,...,i_x\}}^{} v_i-i + \sum_{i \in \{i_1,i_2,...,i_x\}}^{} n -cnt_{i-1} $$假设我们这x个回合分别选的下标集合是{i1,i2,...,ix}
这里的贡献被我们分成了两个部分。那么可以发现后面那一项跟下标集合没有关系,是一个常数项。它的值永远是0+1+2+..+x−1
举个例子:
比如总共有3个回合,我们从里面选2个回合,那么你可以试试,不管是选1,2 还是 2 , 3 还是 1 ,3 , 它的第二项永远是0+1 。
所以说后面那一项可以不看,直接就是
$$\Large = max\ \sum_{i \in \{i_1,i_2,...,i_x\}}^{} v_i-i $$所以我们的做法就是直接对vi−i 排序,取前x 大的位置的vi+n−i−cnti−1 相加即可!
代码
python
n, k = map(int,input().split())
a = list(map(int, input().split()))
b = []	#用于排序的数组
for i in range(len(a)):
	#按照上面的推导,回合数从1开始,第二维应该存i+1,但是不影响排序所以存i即可
	b.append([a[i], i])
b.sort(key = lambda x:(x[0]-x[1]), reverse = True)		#重写排序函数,按照 第一维-第二维 排序,即 v[i]-i
for i in range(k):	#在原数组中将排序后的数组的前x个置为-1,代表在这回合使用塔子起飞
	a[b[i][1]] = -1
ans = 0
cnt = 0	#cnt代表之前用过的塔子起飞次数,ans代表最后受到伤害的总和
for i in a:
	if i == -1:
		cnt += 1		#如果使用塔子起飞,则没有伤害,更新cnt
	else :
		ans += i-cnt	#不使用塔子起飞则恢复cnt点体力,
print(max(ans, 1))
C++
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
int a[100005];
vector<vector<int> >b;
bool cmp(vector<int> x,vector<int> y){	//重写排序函数,按照 第一维-第二维 排序,即 v[i]-i
    return (x[0]-x[1])>(y[0]-y[1]);
}
int main()
{
    int n,k;
    cin>>n>>k;
    for (int i=0;i<n;i++)
        cin>>a[i];
    for (int i=0;i<n;i++)
    {
        vector<int>tmp{a[i],i};	//按照上面的推导,回合数从1开始,第二维应该存i+1,但是不影响排序所以存i即可
        b.push_back(tmp);
    }
    sort(b.begin(),b.end(),cmp);
    for (int i=0;i<k;i++)	//在原数组中将排序后的数组的前x个置为-1,代表在这回合使用塔子起飞
        a[b[i][1]]=-1;
    int ans=0,cnt=0;	//cnt代表之前用过的塔子起飞次数,res代表最后受到伤害的总和
    for (int i=0;i<n;i++)
        if (a[i]==-1)
            cnt+=1;	//如果使用塔子起飞,则没有伤害,更新cnt
        else
            ans+=a[i]-cnt;	//不使用圣光则恢复cnt点塔子起飞,
    cout<<max(ans,1)<<endl;
    return 0;
}
java
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Scanner;
class test {
	public static void main(String[] args) {
		Scanner scanner = new Scanner(System.in);
		int n = scanner.nextInt();
		int k = scanner.nextInt();
		int[] a = new int[n];
		for (int i = 0; i < n; i++)
			a[i] = scanner.nextInt();
		List<List<Integer>> b = new ArrayList<>();
		for (int i = 0; i < n; i++) {
			List<Integer> tmp = new ArrayList<>();
			tmp.add(a[i]);
			tmp.add(i);
			b.add(tmp);
		}
		Collections.sort(b, new Comparator<List<Integer>>() {	//重写排序函数,按照 第一维-第二维 排序,即 v[i]-i
			public int compare(List<Integer> x, List<Integer> y) {
				if (x.get(1) - x.get(0) > y.get(1) - y.get(0))
					return 1;
				return -1;
			}
		});
		for (int i = 0; i < k; i++)	//在原数组中将排序后的数组的前x个置为-1,代表在这回合使用塔子起飞
			a[b.get(i).get(1)] = -1;
		int ans = 0, cnt = 0;	//cnt代表之前用过的塔子起飞次数,res代表最后受到伤害的总和
		for (int i = 0; i < n; i++)
			if (a[i] == -1)	//如果使用塔子起飞,则没有伤害,更新cnt
				cnt++;
			else	//不使用塔子起飞则恢复cnt点体力,
				ans += a[i] - cnt;
		System.out.println(Math.max(ans, 1));
	}
}
js
var in1 = readline().split(' ')
var n = parseInt(in1[0]), k = parseInt(in1[1])
var v = readline().split(' ')
var tmp = []		//用于排序的数组
for(var i = 0 ; i < n ; i++) {
	//按照上面的推导,回合数从1开始,第二维应该存i+1,但是不影响排序所以存i即可
    tmp.push([parseInt(v[i]), i])
}
tmp.sort(function(a,b){	//重写排序函数,按照 第一维-第二维 排序,即 v[i]-i
    return (b[0] - b[1]) - (a[0] - a[1])
})
for(var i = 0 ; i < k ; i++) {	//在原数组中将排序后的数组的前x个置为-1,代表在这回合使用塔子起飞
    v[tmp[i][1]] = -1
}
var res = 0, cnt = 0		//cnt代表之前用过的塔子起飞次数,res代表最后受到伤害的总和
for(var i = 0 ; i < n ; i++) {
    if(v[i] == -1) {
        cnt += 1		//如果使用塔子起飞,则没有伤害,更新cnt
    } else {
        res += parseInt(v[i]) - cnt	//不使用塔子起飞则恢复cnt点体力,
    }
}
console.log(Math.max(res, 1))
go
package main
import (
	"fmt"
	"sort"
)
func main() {
	var n, k int
	fmt.Scan(&n, &k)
	a := make([]int, n)
	for i := 0; i < n; i++ {
		fmt.Scan(&a[i])
	}
	b := make([][]int, n)
	for i := 0; i < n; i++ {
		// Store i instead of i+1 since it doesn't affect sorting
		b[i] = []int{a[i], i}
	}
	sort.SliceStable(b, func(i, j int) bool {
		return b[i][0]-b[i][1] > b[j][0]-b[j][1]
	})
	for i := 0; i < k; i++ {
		a[b[i][1]] = -1
	}
	ans := 0
	cnt := 0
	for _, val := range a {
		if val == -1 {
			cnt++
		} else {
			ans += val - cnt
		}
	}
	if ans < 1 {
		fmt.Println(1)
	} else {
		fmt.Println(ans)
	}
}
        题目内容
塔子最近在玩一款闯关游戏,其共有 m 个回合,塔子有个技能:塔子起飞。总共可以使用 x 次, 以下是对这个技能的简介:
1.每回合使用不超过一次。
2.如果在某一个回合使用了塔子起飞,那么塔子免疫该回合受到的所有伤害,并且在之后没有使用该技能的回合中每回合恢复 1 点体力值
3.技能的效果可以叠加
以下是对塔子起飞技能的案例说明:
前两个回合都释放了一次塔子起飞,那么第三个回合就会恢复 2 点体力。
通关条件:
塔子只需要在 m 个回合后体力大于等于 0 ,即可通关。
问题定义:
请问塔子初始最少需要多少体力。(初始体力应该大于等于 1 , 过程中允许部分时刻体力小于 0 )
输入描述
输入第一-行包含两个整数 m 和 x ,分别表示回合数和塔子起飞使用次数。(1≤m,x≤105)
输入第二行包含 m 个整数,分别表示在第 1 个回合到第 m 个回合中,塔子受到伤害,每回合造成的伤害不超过 105。
输出描述
输出仅包含一个整数,即最少需要的体力值。
样例
输入
5 2
7 3 2 6 5
输出
6