首页 > 试题广场 >

气球谜题

[编程题]气球谜题
  • 热度指数:4206 时间限制:C/C++ 1秒,其他语言2秒 空间限制:C/C++ 256M,其他语言512M
  • 算法知识视频讲解
\hspace{15pt}对于给定的 n 个气球,摆放成一排,颜色仅为红、黄、蓝中的一种。小红想要让颜色相同的气球连续的摆放在一起,为了达成这个目标,她需要将气球重新染色。第 i 个气球重新染成任意的颜色均需要 t_i 秒,询问需要消耗的最少时间是多少呢。

输入描述:
\hspace{15pt}第一行输入一个整数 n \left( 1\leqq n \leqq 2 \times 10^5 \right) 代表气球数量。
\hspace{15pt}第二行输入一个长度为 n 的、仅由 0,1,2 构成的字符串代表气球的初始颜色。其中,0,1,2 分别代表初始颜色为红、黄、蓝。
\hspace{15pt}第三行输入 n 个整数 t_1,t_2,\dots,t_n \left( 1 \leqq t_i \leqq 10^7 \right) 代表气球重新染色需要的时间。


输出描述:
\hspace{15pt}在一行上输出一个整数,代表最少需要的染色时间。
示例1

输入

5
00000
1 2 3 4 5

输出

0

说明

\hspace{15pt}由于初始时全部气球颜色都是一样的,所以不需要重新进行染色。
示例2

输入

6
210102
1 1 4 5 1 4

输出

3

说明

\hspace{15pt}其中一种最优的染色方案是将气球染色为 \texttt{ ,花费 3
示例3

输入

6
001012
1 1 4 5 1 4

输出

3

说明

\hspace{15pt}其中一种最优的染色方案是将气球染色为 \texttt{ ,花费 3
import java.util.Arrays;
import java.util.Scanner;

// 注意类名必须为 Main, 不要有任何 package xxx 信息
public class Main {
    public static void main(String[] args) {
        Scanner in = new Scanner(System.in);
        int n = in.nextInt();
        String s = in.next();
        int[] costs = new int[n];
        for (int i = 0; i < n; i++) {
            costs[i] = in.nextInt();
        }
        // 0 :全红、形如000
        // 1 :全黄、形如111
        // 2 :全蓝、形如222
        // 3 :黄红、形如10
        // 4 :蓝黄、形如21
        // 5 :红蓝、形如02
        // 6 :蓝红、形如20
        // 7 :红黄、形如01
        // 8 :黄蓝、形如12
        // 9 :黄蓝红、形如120
        // 10 :红蓝黄、形如021
        // 11 :黄红蓝、形如102
        // 12 :蓝黄红、形如210
        // 13 :蓝红黄、形如201
        // 14 :红黄蓝、形如012
        int[] t = new int[3]; //t[0]、t[1]、t[2]分别表示更改为红黄蓝颜色需要花费的时间
        long[] dp = new long[15];  //表示更改为对应的颜色类型需要花费的最少时间
        for (int i = 0; i < s.length(); i++) {
            t[0] = t[1] = t[2] = costs[i];
            t[s.charAt(i) - '0'] = 0; //改为当前颜色的花费为0
            dp[14] = Math.min(dp[14], dp[7]) + t[2];  //红黄蓝只能由红黄蓝本身以及红黄变过来
            dp[13] = Math.min(dp[13], dp[6]) + t[1];
            dp[12] = Math.min(dp[12], dp[4]) + t[0];
            dp[11] = Math.min(dp[11], dp[3]) + t[2];
            dp[10] = Math.min(dp[10], dp[5]) + t[1];
            dp[9] = Math.min(dp[9], dp[8]) + t[0];
            dp[8] = Math.min(dp[8], dp[1]) + t[2];
            dp[7] = Math.min(dp[7], dp[0]) + t[1];
            dp[6] = Math.min(dp[6], dp[2]) + t[0];
            dp[5] = Math.min(dp[5], dp[0]) + t[2];
            dp[4] = Math.min(dp[4], dp[2]) + t[1];
            dp[3] = Math.min(dp[3], dp[1]) + t[0];
            dp[2] += t[2];
            dp[1] += t[1];
            dp[0] += t[0];
        }

        long minV =  Long.MAX_VALUE;
        for (int i = 0; i < 15; i++) {
            minV = Math.min(minV, dp[i]);
        }
        System.out.println(minV);
    }
}


发表于 2025-04-04 20:00:26 回复(0)
参考官方题解:周赛71-D_哔哩哔哩_bilibili
定义/初始化/转移
定义:
dp[i, j, k]:前 i 个气球(一般想到动态规划都会往前 i 个靠拢),第 i 个气球的颜色是 j (这是因为我们需要判断气球是否要变色,因而必须要知道末端的气球颜色,显然有 j 属于 {0, 1, 2} )且前 i 个气球所拥有的颜色集合 k (颜色相同的气球摆放在一起,因而变色后不能是“11001”这类情况,故变色时需要知道之前出现过的颜色,使用二进制表示简单易懂占用空间小,例如用2,即100表示数字2已出现、用3,即110表示数字2和1已出现)的花费最小值
初始化:
求最小值,故将所有值更新为最大值
对第1个气球,枚举颜色 j' :如果 str(1) == j',那么不需要染色(0);如果 str(1) != j',需要染色(t[1])
转移:
1. 第 i 个气球是否染色:
设第 i 个气球的颜色是 j,前 i 个气球所拥有的颜色集合为 k,第 i - 1个气球的颜色是 j',前 i - 1个气球所拥有的颜色集合为 k',枚举颜色为 mj ;其中 k = k' | ( 1<< j )
染色( str(i) != mj): dp( i, j, k ) <----- dp( i-1, j', k') + t(i)
不染色 ( str(i) == mj ):dp( i, j, k ) <----- dp( i-1, j', k') + 0
2. 第 i - 1个气球的颜色 j' 是否等于第 i 个气球的颜色 j
下面的 k 表示前 i - 1个气球包含颜色的集合 
等于:dp( i, j, k ) <--------- dp( i-1, j, k)
不等于:dp( i, j, k | (1<<j) ) <---------- dp( i-1, j', k)
#include <bits/stdc++.h>
using namespace std;

const int N = 2 * 1e5 + 100;
const long long M = 2 * 1e12 + 100;

int main() {
    int n;
    string str;
    vector<int> t(N);
    
    cin >> n >> str;
    str = "?" + str;
    for (int i = 1; i <= n; i++) {
        cin >> t[i];
    }

    vector<vector<vector<long long>>> dp(N, vector<vector<long long>>(3, vector<long long>(8, M)));
    for (int j = 0; j < 3; j++) {
        dp[1][j][1 << j] = str[1] - '0' == j ? 0 : t[1];
    }

    for (int i = 2; i <= n; i++) {
        for (int j = 0; j < 3; j++) {
            for (int mask = 0; mask < 8; mask++) {
                if (mask >> j & 1) { // 第 i 个气球的颜色为 j,mask含 j
                    dp[i][j][mask] = min(dp[i][j][mask], dp[i - 1][j][mask] + (str[i] - '0' == j ? 0 : t[i]));
                }
                else { // 第 i 个气球的颜色为 j,mask不含 j,找出第 i-1 个气球所有符合条件的情况,用dp[i - 1][k][mask]推其他值。因为mask不含 j,故其他值是dp[i][j][mask | (1 << j)]
                    for (int k = 0; k < 3; k++) {
                        if (mask >> k & 1 && k != j) {
                            dp[i][j][mask | (1 << j)] = min(dp[i][j][mask | (1 << j)], dp[i - 1][k][mask] + (str[i] - '0' == j ? 0 : t[i]));  
                        }
                    }
                }
            }
        }
    }
}


发表于 2025-12-03 18:37:37 回复(0)
6种排列比较但是过不了第一个用例,不知道错在哪里
import java.util.*;

public class Main {
    static int[] spend;
    static int[] colorCount = new int[3]; // 0: red, 1: yellow, 2: blue

    public static void main(String[] args) {
        Scanner in = new Scanner(System.in);
        int n = in.nextInt();
        in.nextLine();
        String str = in.nextLine();
        spend = new int[n];
        for (int i = 0; i < n; i++) {
            spend[i] = in.nextInt();
        }

        // 统计每种颜色的数量
        for (int i = 0; i < n; i++) {
            char c = str.charAt(i);
            if (c == '0') colorCount[0]++;
            else if (c == '1') colorCount[1]++;
            else colorCount[2]++;
        }

        int minTime = Integer.MAX_VALUE;
        // 6 种可能的排列方式
        minTime = Math.min(minTime, calculateTime(str, 0, 1, 2));
        minTime = Math.min(minTime, calculateTime(str, 0, 2, 1));
        minTime = Math.min(minTime, calculateTime(str, 1, 0, 2));
        minTime = Math.min(minTime, calculateTime(str, 1, 2, 0));
        minTime = Math.min(minTime, calculateTime(str, 2, 0, 1));
        minTime = Math.min(minTime, calculateTime(str, 2, 1, 0));

        System.out.println(minTime);
    }

    public static int calculateTime(String str, int first, int second, int third) {
        int time = 0;
        int r1 = colorCount[first];
        int r2 = colorCount[second];
        int r3 = colorCount[third];

        // 第一部分:检查前 r1 个位置是否应该是 first 颜色
        for (int i = 0; i < r1; i++) {
            char c = str.charAt(i);
            if (c - '0' != first) {
                time += spend[i];
            }
        }

        // 第二部分:检查接下来的 r2 个位置是否应该是 second 颜色
        for (int i = r1; i < r1 + r2; i++) {
            char c = str.charAt(i);
            if (c - '0' != second) {
                time += spend[i];
            }
        }

        // 第三部分:检查剩下的位置是否应该是 third 颜色
        for (int i = r1 + r2; i < str.length(); i++) {
            char c = str.charAt(i);
            if (c - '0' != third) {
                time += spend[i];
            }
        }

        return time;
    }
}

发表于 2025-09-08 17:02:39 回复(0)
#时间对比中相对论
import sys

n = int(sys.stdin.readline())  # 任务数量
colors = sys.stdin.readline().strip()  # 每个任务的颜色
times = list(map(int, sys.stdin.readline().split()))  # 每个任务的处理时间
total = sum(times)  # 总处理时间
permutations = [
    ('0', '1', '2'), ('0', '2', '1'),
    ('1', '0', '2'), ('1', '2', '0'),
    ('2', '0', '1'), ('2', '1', '0'),
]  #permutations:排列;序列
# 所有可能的颜色排列(处理器处理颜色的顺序)
min_cost = float('inf') 

for perm in permutations:
    a, b, c = perm   
    dp0 = 0  # 处理器A的初始负载
    dp1 = -float('inf') # 处理器B的初始负载(尚未开始处理)
    dp2 = -float('inf') # 处理器C的初始负载(尚未开始处理)
    for i in range(n):
        current_color = colors[i] #每个任务的颜色
        t = times[i]        
        if current_color == a:
            new_dp0 = dp0 + t
            new_dp1 = dp1
            new_dp2 = dp2
        elif current_color == b:
            new_dp0 = dp0
            new_dp1 = max(dp1 + (t if dp1 != -float('inf') else 0), dp0 + t)
            new_dp2 = dp2
        else:
            new_dp0 = dp0
            new_dp1 = dp1
            new_dp2 = max(dp2 + (t if dp2 != -float('inf') else 0), dp1 + t)
        dp0, dp1, dp2 = new_dp0, new_dp1, new_dp2
     # 计算当前排列下的最大负载
    current_max = max(dp0, dp1 if dp1 != -float('inf') else 0, dp2 if dp2 != -float('inf') else 0)
    current_cost = total - current_max  # 目标是最小化这个值
    if current_cost < min_cost:
        min_cost = current_cost
print(min_cost)




发表于 2025-07-17 22:44:46 回复(0)
不知道为什么只能过80%的案例
n = int(input())
s = input().strip()
t = list(map(int, input().split()))

# 构造前后缀和数组
prefix = [[0] * (n + 1) for _ in range(3)]
for c in range(3):
for i in range(1, n + 1):
prefix[c][i] = prefix[c][i - 1] + (t[i - 1] if int(s[i - 1]) != c else 0)

suffix = [[0] * (n + 1) for _ in range(3)]
for c in range(3):
for i in range(n - 1, -1, -1):
suffix[c][i] = suffix[c][i + 1] + (t[i] if int(s[i]) != c else 0)

# 单元素最小耗费
min_single = min(prefix[0][n], prefix[1][n], prefix[2][n])

min_double = float("inf")
min_triple = float("inf")

#对给定颜色对计算最小耗费
def update_min_double(c1, c2):
global min_double
current_min = float("inf")
for k in range(n + 1):
cost = prefix[c1][k] + suffix[c2][k]
if cost < current_min:
current_min = cost
if current_min < min_double:
min_double = current_min

#遍历所有色对
for c1 in range(3):
for c2 in range(3):
if c1 != c2:
update_min_double(c1, c2)

#三元色组最小花费计算
def update_min_triple(perm):
global min_triple
c1, c2, c3 = perm
min_diff = [0] * (n + 1)
min_val = float("inf")
for k in range(n + 1):
current = prefix[c1][k] - prefix[c2][k]
if current < min_val:
min_val = current
min_diff[k] = min_val
for k2 in range(n + 1):
cost = min_diff[k2] + prefix[c2][k2] + suffix[c3][k2]
if cost < min_triple:
min_triple = cost

# 遍历所有三元色组
from itertools import permutations

for perm in permutations([0, 1, 2]):
update_min_triple(perm)

result = min(min_single, min_double, min_triple)
print(result)

发表于 2025-06-30 22:07:27 回复(0)
n=int(input())
color=input().strip()
t=list(map(int,input().split()))
cost0,cost1,cost2=[0]*(n+1),[0]*(n+1),[0]*(n+1)#用来记录将字符串的前i个元素全部转化为指定元素(0,1,2)所需的时间
for i in range(len(color)):
    if color[i]=='0':
        cost0[i+1]=cost0[i]
        cost1[i+1]=cost1[i]+t[i]
        cost2[i+1]=cost2[i]+t[i]
    elif color[i]=='1':
        cost0[i+1]=cost0[i]+t[i]
        cost1[i+1]=cost1[i]
        cost2[i+1]=cost2[i]+t[i]
    elif color[i]=='2':
        cost0[i+1]=cost0[i]+t[i]
        cost1[i+1]=cost1[i]+t[i]
        cost2[i+1]=cost2[i]
def fun(color0,color1,color2):
    #我们要找到i,j两个切点将字符串分成三段,分别换成(0,1,2)的一种排列,时间为t=colors0[i]+colors1[j]-colors1[i]+colors2[-1]-colors2[j] 转化一下变成t=colors0[i]-colors1[i]+colors1[j]-colors2[j]+colors2[-1],也就是说因为第一个切点i肯定在第二个切点j的前面,所以我们只需要根据第二个切点j进行遍历,计算colors0[i]-colors1[i]的最小值(i<=j),最后算出的t取最小

    colors0=[cost0,cost1,cost2][color0]
    colors1=[cost0,cost1,cost2][color1]
    colors2=[cost0,cost1,cost2][color2]
    min_pre=colors0[1]-colors1[1]#当第二个切点在第一个元素后面,显然第一个切点有且只有一种情况,也在第一个元素后面(至于第一个元素前面的可能性,我写的切点全是在元素的后面,至于第一个切点在第一个元素前面本质上就是只用两种颜色,和在最后一个元素后面是一样的)
    t=min_pre+colors1[1]-colors2[1]+colors2[-1]#计算第二个切点在第一个元素后面的最小时间,因为i,j只有一种可能所以直接算即可
    for j in range(1,n):
        min_pre=min(colors0[j+1]-colors1[j+1],min_pre)#随着第二个切点j后移,只需要取第二个切点在j-1处和j的最小值
        t=min(t,min_pre+colors1[j+1]-colors2[j+1]+colors2[-1])#时间也是一样的
    return t

time=[]
for order in [(0, 1, 2), (0, 2, 1), (1, 0, 2), (1, 2, 0), (2, 0, 1), (2, 1, 0)]:
    time.append(fun(*order))
print(min(time))
#本方法对于6种排列分别求最短的时间,只用循环一次,时间复杂度为O(n),感觉比排行上的更简单但是运行时间1725ms,不知道为什么
发表于 2025-06-24 17:54:54 回复(0)
//理解之后不难 分析发现一共有15中情况(即单色 双色 三色) 但其实不需要遍历15次 单色每次都需要遍历 共3次 双色基于单色的状态机 三色基于双色的状态机 不需要重头开始

import java.util.Arrays;
import java.util.Scanner;

// 注意类名必须为 Main, 不要有任何 package xxx 信息
public class Main {
    public static void main(String[] args) {
        Scanner in = new Scanner(System.in);
        // 注意 hasNext 和 hasNextLine 的区别
        int n = in.nextInt();
        in.nextLine();
        String a = in.nextLine();
        int[] data = new int[n];
        for(int i = 0;i<n;i++){
            data[i] = a.charAt(i)-'0';
        }
        int[] time = new int[n];
        for(int i = 0;i<n;i++){
            time[i] = in.nextInt();
        }
        if(n==0){
            System.out.println(0);
            return;
        }
        int[] team = {0,1,2,
                1,2,0,
                2,0,1,
                2,1,2,
                0,1,0};
        /*
        列出15种情况
        0红
        1黄
        2蓝
        3红黄1
        4红蓝2
        5黄红0
        6黄蓝2
        7蓝红0
        8蓝黄1
        9红黄蓝2
        10红蓝黄1
        11黄红蓝2
        12黄蓝红0
        13蓝红黄1
        14蓝黄红0
         */
        long[] pre = new long[15];
        Arrays.fill(pre,Long.MAX_VALUE/10);
        pre[0] = (data[0]==0)?0:time[0];
        pre[1] = (data[0]==1)?0:time[0];
        pre[2] = (data[0]==2)?0:time[0];

        for(int i = 1;i<n;i++){
            long[] now = new long[15];
            Arrays.fill(now,Long.MAX_VALUE/10);

            int[] cost = {(data[i]==0)?0:time[i],
                    (data[i]==1)?0:time[i],
                    (data[i]==2)?0:time[i]};

            //处理单色状态0-2
            for(int j = 0;j<3;j++){
                now[j] = Math.min(now[j],pre[j]+cost[j]);
            }
            //处理双色状态3-8
            for(int j = 3;j<9;j++){
                int temp = (j-3)/2;
                now[j] = Math.min(pre[temp], pre[j])+cost[team[j]];
            }
            //处理三色状态9-14
            for(int j = 9;j<15;j++){
                now[j] = Math.min(pre[j-6], pre[j])+cost[team[j]];
            }
            pre = now;

        }
        long min = pre[0];
        for(int i = 1;i<pre.length;i++){
            min = Math.min(min,pre[i]);
        }
        System.out.println(min);
    }
}
发表于 2025-04-04 22:37:31 回复(0)
202这种情况时不满足条件的,202之间2断开了
发表于 2025-03-25 16:59:58 回复(0)
我用excel算了这个用例,答案就是2572,程序判断有问题,我的代码参考如下
100
2112211000202002110122220012110100110022010102200201202122022210001002120112010210021012001212010210
49 77 40 28 38 55 17 53 33 68 57 7 56 78 77 68 99 82 41 26 64 69 1 98 98 63 15 13 95 23 71 6 57 4 10 10 44 11 84 10 59 62 94 49 7 46 37 87 79 6 85 91 42 42 34 94 51 89 98 29 12 64 97 23 20 97 80 17 38 76 83 65 40 41 55 3 16 56 13 11 74 31 24 9 76 4 51 99 76 25 93 39 37 74 23 46 19 54 57 14

public static void main(String[] args) {
        Scanner in = new Scanner(System.in);
        int total = Integer.valueOf(in.nextLine());
        String str = in.nextLine();
        int[] weight = new int[total];
        for (int i = 0; i < total; i++) {
            weight[i] = in.nextInt();
        }
        if (str.matches("[0]+") || str.matches("[1]+") || str.matches("[2]+")) {
            System.out.println(0);
            return;
        }
        int result = -1;
        for (int i = 1; i < total; i++) {
            for (int j = i + 1; j < total; j++) {
                int result1 = dfs(str, 0, i, weight);
                int result2 = dfs(str, i, j, weight);
                int result3 = dfs(str, j, total, weight);
                int sum = result1 + result2 + result3;
                result = result == -1 ? sum : Math.min(result, sum);
            }
        }
        System.out.println(result);
    }

    static Map<String, Integer> map = new LinkedHashMap<>();

    public static int dfs(String input, int x, int y, int[] weight) {
        if (map.containsKey(x + ":" + y)) {
            return map.get(x + ":" + y);
        }
        int result0 = 0;
        int result1 = 0;
        int result2 = 0;
        for (int i = x; i < y; i++) {
            char ch = input.charAt(i);
            // 改成0
            if (ch != '0') {
                result0 += weight[i];
            }
            // 改成1
            if (ch != '1') {
                result1 += weight[i];
            }
            // 改成2
            if (ch != '2') {
                result2 += weight[i];
            }
        }
        int min = Math.min(Math.min(result0, result1), result2);
        map.put(x + ":" + y, min);
        return min;
    }

发表于 2025-03-21 22:11:59 回复(1)