#P2708. 第2题-字符串数组
          
                        
                                    
                      
        
              - 
          
          
                      1000ms
            
          
                      Tried: 39
            Accepted: 14
            Difficulty: 4
            
          
          
          
                       所属公司 : 
                              阿里
                                
            
                        
              时间 :2025年3月16日-阿里云(开发岗)
                              
                      
          
 
- 
                        算法标签>贪心算法          
 
第2题-字符串数组
题解
题面描述
给定一个长度为 n 的数组 a 以及一个长度为 n 的仅由字符 ′0′ 和 ′1′ 组成的字符串 s。每个位置 i 对应一个操作符:
- 若 si=′1′,则 op(i)=1;
 - 若 si=′0′,则 op(i)=−1。
 
我们可以将数组切分为至多 k 块(也就是最多有 k−1 个切割点)。对于数组中每个位置 i,若其所在块的编号为 j(块的编号从 1 开始),则该位置的贡献为 op(i)×(ai+j) 数组的总权值即所有位置贡献的和。要求我们选择合适的切分方法,使得数组的总权值最大。
思路分析
首先我们注意到,每个位置 i 的贡献可以拆分为两部分:
- 与 ai 相关的部分:op(i)×ai
 - 与块编号相关的部分:op(i)×j
 
由于不论如何切分,所有位置至少所在块编号为 1,所以对于第二部分,每个位置最少贡献 op(i)×1。令 base = ∑i=1nop(i)×(ai+1) 那么如果我们没有进行任何切分,总权值就等于 base。
当我们在数组中插入一个切割点时,假设在位置 i(即切在 i 与 i+1 之间),那么 i+1 及其之后的所有位置块编号都会增加 1。因此,这个切割点会为答案增加的额外贡献为: contrib(i)=∑j=i+1nop(j)
由于最多可以切出 k 块,也就是最多可以插入 k−1 个切割点,我们只需要从所有可能的切割点(位置 1 到 n−1)中选取最多 k−1 个,使得所有选中切割点的 contrib 之和最大。并且注意,如果某个切割点的贡献为负,则我们不选取它。
最终答案即为 base + ∑选中的切割点 i贡献>0 contrib(i)
C++
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
typedef long long ll;
int main(){
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    int n, k;
    cin >> n >> k;
    vector<ll> a(n);
    for (int i = 0; i < n; i++){
        cin >> a[i];
    }
    string s;
    cin >> s;
    
    // 计算 op 数组, op[i] = 1 如果 s[i]=='1', 否则 -1
    vector<int> op(n);
    for (int i = 0; i < n; i++){
        op[i] = (s[i] == '1') ? 1 : -1;
    }
    
    // 计算 base = sum_{i=1}^{n} op(i) * (a_i + 1)
    ll base = 0;
    for (int i = 0; i < n; i++){
        base += op[i] * (a[i] + 1);
    }
    
    // 计算从后向前的后缀和, 用于每个可能切割点的贡献
    vector<ll> suffix(n, 0);
    suffix[n-1] = op[n-1];
    for (int i = n - 2; i >= 0; i--){
        suffix[i] = suffix[i+1] + op[i];
    }
    
    // 对于切割点, 即在位置 i (0<=i<=n-2) 切分, 贡献为 suffix[i+1]
    vector<ll> contributions;
    for (int i = 0; i < n - 1; i++){
        if(suffix[i+1] > 0) {
            contributions.push_back(suffix[i+1]);
        }
    }
    
    // 将所有正贡献排序,选取最多 k-1 个贡献最大的
    sort(contributions.begin(), contributions.end(), greater<ll>());
    ll extra = 0;
    int cuts = min((int)contributions.size(), k - 1);
    for (int i = 0; i < cuts; i++){
        extra += contributions[i];
    }
    
    cout << base + extra << "\n";
    return 0;
}
Python
# 读取输入
n, k = map(int, input().split())
a = list(map(int, input().split()))
s = input().strip()
# 计算 op 数组,若 s[i]=='1' 则 op[i]=1,否则为 -1
op = [1 if ch=='1' else -1 for ch in s]
# 计算 base = sum(op[i]*(a[i]+1))
base = sum(op[i]*(a[i]+1) for i in range(n))
# 计算后缀和数组,用于计算每个可能切割点的贡献
suffix = [0] * n
suffix[n-1] = op[n-1]
for i in range(n-2, -1, -1):
    suffix[i] = suffix[i+1] + op[i]
# 对于每个可能的切割点(位置 0 到 n-2),贡献为 suffix[i+1]
contributions = []
for i in range(n-1):
    if suffix[i+1] > 0:
        contributions.append(suffix[i+1])
# 对贡献进行降序排序,选取最多 k-1 个
contributions.sort(reverse=True)
extra = sum(contributions[:max(0, k-1)])
# 输出最终答案
print(base + extra)
Java
import java.util.*;
import java.io.*;
public class Main {
    public static void main(String[] args) throws IOException {
        // 使用 BufferedReader 提高输入效率
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        String[] nk = br.readLine().trim().split(" ");
        int n = Integer.parseInt(nk[0]);
        int k = Integer.parseInt(nk[1]);
        
        // 读取数组 a
        String[] aStr = br.readLine().trim().split(" ");
        long[] a = new long[n];
        for (int i = 0; i < n; i++){
            a[i] = Long.parseLong(aStr[i]);
        }
        
        // 读取字符串 s
        String s = br.readLine().trim();
        
        // 计算 op 数组,若 s.charAt(i)=='1' 则 op[i]=1,否则为 -1
        int[] op = new int[n];
        for (int i = 0; i < n; i++){
            op[i] = (s.charAt(i) == '1') ? 1 : -1;
        }
        
        // 计算 base = sum(op[i]*(a[i]+1))
        long base = 0;
        for (int i = 0; i < n; i++){
            base += op[i] * (a[i] + 1);
        }
        
        // 计算后缀和数组 suffix,suffix[i] = op[i] + op[i+1] + ... + op[n-1]
        long[] suffix = new long[n];
        suffix[n-1] = op[n-1];
        for (int i = n - 2; i >= 0; i--){
            suffix[i] = suffix[i+1] + op[i];
        }
        
        // 对每个可能的切割点(位置 0 到 n-2),贡献为 suffix[i+1]
        ArrayList<Long> contributions = new ArrayList<>();
        for (int i = 0; i < n - 1; i++){
            if(suffix[i+1] > 0){
                contributions.add(suffix[i+1]);
            }
        }
        
        // 将贡献降序排序,选取最多 k-1 个贡献最大的
        Collections.sort(contributions, Collections.reverseOrder());
        long extra = 0;
        int cuts = Math.min(contributions.size(), k - 1);
        for (int i = 0; i < cuts; i++){
            extra += contributions.get(i);
        }
        
        // 输出最终答案
        System.out.println(base + extra);
    }
}
        题目内容
小红有一个长度为n的数组a和一个度为n的字符串s。她最多可以将数组切割成1块。
定义数组的权值为所有元素的权值之和。对于数组中的第1个元素,其权值计算方式为:op(i)×(a+j)
op(i)的值取决手字符串s的第i个字符:
- 
若si='1',则op(i)=1
 - 
若si='0’,则op(i)=−1
 - 
j表示ai所在的块的编号(从1开始)
 
小红想要通过合理的切割方式,使得数组的总权值最大,请你帮她计算出可能的最大权值。
输入描述
第一行包含两个正整数n和k,表示数组的长度和数组最多的块数。
第二行包含n个整数a1,a2,...an,表示数组a
第三行包含一个长度为n的字符串s,仅由字符‘0’和'1'组成
1≦k≦n
2≤n≤105
1≤ai≤109
输出描述
输出一个整数。表示数组可能的最大权值。
样例1
输入
4 2
1 2 3 4
1001
输出
1
说明
一种最优的切割方案是将数组切成[1,2,3]和[4]两块。