首页 > 试题广场 >

小红的双生英雄

[编程题]小红的双生英雄
  • 热度指数:2358 时间限制:C/C++ 1秒,其他语言2秒 空间限制:C/C++ 256M,其他语言512M
  • 算法知识视频讲解
\hspace{15pt}小红有 n 个英雄,第 i 个英雄的 cost 是 a_i,战斗力为 b_i。另外,存在一些“双生英雄”关系,如果同时上阵某一对英雄,则可以获得额外的战斗力收益。
\hspace{15pt}现在小红最多可以上阵四名英雄,且要求总 cost 不超过C。小红希望你求出组队的最大战斗力。

输入描述:
\hspace{15pt}第一行输入三个整数 n,C,m \left(1 \leqq n,C \leqq 10^3;\ 0 \leqq m \leqq n/2\right) 代表英雄数量、总cost限制、双生英雄的对儿数。 
\hspace{15pt}此后 n 行,第 i 行输入两个正整数 a_i,b_i \left(1 \leqq a_i,b_i \leqq 10^9\right) 代表第 i 个英雄的 cost 和战斗力。
\hspace{15pt}此后 m 行,第 i 行输入三个正整数 u_i,v_i,w_i \left(1 \leqq u_i,v_i \leqq n; u_i \neq v_i; 1 \leqq w_i \leqq 10^9\right) 代表第 u_i 和第 v_i 个英雄是双生英雄,同时上阵可以额外增加 w_i 战斗力。

\hspace{15pt}除此之外,保证每个英雄最多只存在一对双生英雄关系。对于 40\% 的用例,额外保证 m=0


输出描述:
\hspace{15pt}输出一个整数,代表组队的最大战斗力。
示例1

输入

4 10 1
3 9
4 10
6 15
8 20
1 2 15

输出

34

说明

\hspace{15pt}在这个样例中,小红可以选择上阵第一、二个英雄,获得 9+10+15=34 的战斗力。我们可以证明,这是符合要求的最大战斗力。
示例2

输入

4 1 1
3 9
4 10
6 15
8 20
1 2 15

输出

0

算法逻辑说明

  • 使用 动态规划dp[i][k][j] 表示前 i 个英雄中选了 k 个,总 cost 为 j 时的最大战斗力。
  • 使用滚动数组优化空间(只保留两层)。
  • 每个英雄最多属于一个“双生对”,处理时避免重复。
  • 对于双生英雄 (u, v),只有当两者都被选时,才加上额外战斗力 w
  • 遍历每个英雄,分情况更新 DP:
  • 只选当前英雄
  • 只选其配对英雄(如果前面已处理)
  • 同时选当前和配对英雄(触发双生加成)

package main

import (
	"bufio"
	"fmt"
	"os"
	"strconv"
	"strings"
)

type Hero struct {
	cost, v, tp, fnv int64
}

func main() {
	reader := bufio.NewReader(os.Stdin)

	// 读取第一行 n, C, m
	line, _ := reader.ReadString('\n')
	parts := strings.Fields(line)
	n, _ := strconv.Atoi(parts[0])
	costLimit, _ := strconv.Atoi(parts[1])
	m, _ := strconv.Atoi(parts[2])

	// 初始化英雄数组
	heroes := make([]Hero, n+1) // 1-indexed

	// 读取 n 个英雄的 cost 和战斗力
	for i := 1; i <= n; i++ {
		line, _ = reader.ReadString('\n')
		parts = strings.Fields(line)
		a, _ := strconv.ParseInt(parts[0], 10, 64)
		b, _ := strconv.ParseInt(parts[1], 10, 64)
		heroes[i] = Hero{cost: a, v: b}
	}

	// 读取 m 对双生英雄
	for i := 0; i < m; i++ {
		line, _ = reader.ReadString('\n')
		parts = strings.Fields(line)
		u, _ := strconv.Atoi(parts[0])
		v, _ := strconv.Atoi(parts[1])
		w, _ := strconv.ParseInt(parts[2], 10, 64)

		heroes[u].tp = int64(v)
		heroes[v].tp = int64(u)
		heroes[u].fnv = w
		heroes[v].fnv = w
	}

	// DP 数组:dp[滚动][k个英雄][总cost] -> 最大战斗力
	// dp[i&1][k][j] 滚动使用
	const MAX_K = 5
	const MAX_COST = 1001
	var dp [2][MAX_K][MAX_COST]int64

	// 初始化 dp 为 0(Go 默认为 0,安全)
	// 使用滚动数组:cur 和 prev
	for i := 1; i <= n; i++ {
		cur := i & 1
		prev := cur ^ 1

		// 拷贝上一行状态
		for k := 0; k < MAX_K; k++ {
			for j := 0; j <= costLimit; j++ {
				dp[cur][k][j] = dp[prev][k][j]
			}
		}

		tp := heroes[i].tp
		if tp != 0 && int64(tp) < int64(i) {
			// 避免重复处理双生对,只在较大的索引处理
			continue
		}

		// 情况1:只选当前英雄 i
		for k := 1; k < MAX_K; k++ {
			for j := int(heroes[i].cost); j <= costLimit; j++ {
				prevVal := dp[prev][k-1][j-int(heroes[i].cost)]
				if prevVal+heroes[i].v > dp[cur][k][j] {
					dp[cur][k][j] = prevVal + heroes[i].v
				}
			}
		}

		// 如果没有配对英雄,跳过后续双生逻辑
		if tp == 0 {
			continue
		}

		// 情况2:只选配对英雄 tp
		for k := 1; k < MAX_K; k++ {
			tpCost := int(heroes[tp].cost)
			if tpCost > costLimit {
				continue
			}
			for j := tpCost; j <= costLimit; j++ {
				prevVal := dp[prev][k-1][j-tpCost]
				if prevVal+heroes[tp].v > dp[cur][k][j] {
					dp[cur][k][j] = prevVal + heroes[tp].v
				}
			}
		}

		// 情况3:同时选 i 和 tp,触发双生加成
		totalCost := int(heroes[i].cost + heroes[tp].cost)
		if totalCost <= costLimit {
			for k := 2; k < MAX_K; k++ {
				for j := totalCost; j <= costLimit; j++ {
					prevVal := dp[prev][k-2][j-totalCost]
					totalV := prevVal + heroes[i].v + heroes[tp].v + heroes[i].fnv
					if totalV > dp[cur][k][j] {
						dp[cur][k][j] = totalV
					}
				}
			}
		}
	}

	// 在所有选 0~4 个英雄、总 cost <= costLimit 的状态中找最大值
	ans := int64(0)
	final := n & 1
	for k := 0; k < MAX_K; k++ {
		if dp[final][k][costLimit] > ans {
			ans = dp[final][k][costLimit]
		}
	}

	fmt.Println(ans)
}


发表于 2025-09-03 16:27:39 回复(0)