题目链接
题目描述
火星科研团队发现了 颗性质各异的原子,编号依次为
。他们可以任取其中两颗原子进行碰撞,碰撞后,其中一颗原子会消失并从系统中移除,同时产生一定的能量。这个过程会一直持续,直到只剩下一颗原子为止。
研究人员已经提前测得了任意两颗原子 和
发生碰撞,如果原子
消失,能够产生的能量为
。为了获得尽可能多的总能量,他们可以自由决定碰撞的顺序,以及每次碰撞中哪颗原子消失。你需要帮助研究人员设计碰撞顺序,使得所有
次碰撞产生的能量之和最大。
解题思路
这是一个寻找最优决策序列以获取最大收益的问题。观察到原子数量 的范围非常小(通常
),这是使用状态压缩动态规划(State Compression DP)的典型信号。
我们的目标是记录一系列碰撞后获得的最大能量。一个碰撞过程可以用当前哪些原子“存活”或“消失”来描述。因此,我们可以用一个整数的二进制位来表示所有原子的状态集合。
状态定义
我们定义一个 DP 数组 ,其中
是一个
位的二进制数(位掩码),它的第
位为
表示第
颗原子已经消失,为
则表示第
颗原子仍然存在。
的值就表示:在
所描述的原子消失集合下,可以获得的最大总能量。
状态转移
我们的目标是计算出所有可能的 。我们可以通过枚举当前状态,然后考虑下一步可能发生的碰撞来更新后续状态的值。这是一种“推”的 DP 思路。
假设我们当前处于状态 ,这意味着集合
中的原子已经消失了。此时,我们可以从仍然存在的原子中,任意挑选两颗,比如
和
(即
的第
位和第
位都为
),让它们进行碰撞。
-
如果让原子
消失:
- 系统获得能量
。
- 状态将从
转移到一个新的状态
,其中原子
也消失了。这个新状态
的位掩码就是
。
- 我们用当前的总能量
来更新新状态的 DP 值。即:
- 系统获得能量
-
同理,如果让原子
消失,状态会转移到
,并获得能量
。
我们可以遍历所有当前状态 ,再遍历所有可能的碰撞组合
,用上述方法更新所有可达的未来状态。
初始状态
- 最开始时,没有任何原子消失,所以初始状态是
。此时的总能量为
,即
。
最终答案
- 整个过程会进行
次碰撞,最终会留下
颗原子,也就是有
颗原子消失。
- 因此,最终的答案就是所有包含了
个消失原子(即二进制表示中有
个
)的状态
中,
的最大值。
整个算法的流程如下:
- 初始化
数组,设置
,其余为
。
- 遍历所有状态
从
到
。
- 对于每个状态
,遍历所有可能的原子对
。
- 如果原子
和
在状态
中都还存在,则模拟它们碰撞并分别计算两种消失情况(
消失或
消失)带来的能量增益,并更新对应的新状态的
值。
- 所有状态更新完毕后,遍历所有满足
的状态
,找出其中最大的
值即为答案。
代码
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
int main() {
int n;
while (cin >> n && n != 0) {
vector<vector<int>> energy(n, vector<int>(n));
for (int i = 0; i < n; ++i) {
for (int j = 0; j < n; ++j) {
cin >> energy[i][j];
}
}
vector<long long> dp(1 << n, 0);
for (int s = 0; s < (1 << n); ++s) {
for (int i = 0; i < n; ++i) {
// 检查原子 i 是否存在
if (!((s >> i) & 1)) {
for (int j = 0; j < n; ++j) {
// 检查原子 j 是否存在,且不与 i 相同
if (i != j && !((s >> j) & 1)) {
// i 与 j 碰撞,j 消失
int next_s = s | (1 << j);
dp[next_s] = max(dp[next_s], dp[s] + energy[i][j]);
}
}
}
}
}
long long max_energy = 0;
for (int s = 0; s < (1 << n); ++s) {
int set_bits = 0;
for (int i = 0; i < n; ++i) {
if ((s >> i) & 1) {
set_bits++;
}
}
if (set_bits == n - 1) {
max_energy = max(max_energy, dp[s]);
}
}
cout << max_energy << endl;
}
return 0;
}
import java.util.Scanner;
import java.util.Arrays;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n;
while ((n = sc.nextInt()) != 0) {
int[][] energy = new int[n][n];
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
energy[i][j] = sc.nextInt();
}
}
long[] dp = new long[1 << n];
Arrays.fill(dp, 0);
for (int s = 0; s < (1 << n); s++) {
for (int i = 0; i < n; i++) {
// 检查原子 i 是否存在
if ((s & (1 << i)) == 0) {
for (int j = 0; j < n; j++) {
// 检查原子 j 是否存在,且不与 i 相同
if (i != j && (s & (1 << j)) == 0) {
// i 与 j 碰撞,j 消失
int nextS = s | (1 << j);
dp[nextS] = Math.max(dp[nextS], dp[s] + energy[i][j]);
}
}
}
}
}
long maxEnergy = 0;
for (int s = 0; s < (1 << n); s++) {
if (Integer.bitCount(s) == n - 1) {
maxEnergy = Math.max(maxEnergy, dp[s]);
}
}
System.out.println(maxEnergy);
}
}
}
import sys
for line in sys.stdin:
n = int(line)
if n == 0:
break
energy = []
for _ in range(n):
energy.append(list(map(int, sys.stdin.readline().split())))
dp = [0] * (1 << n)
for s in range(1 << n):
for i in range(n):
# 检查原子 i 是否存在
if not ((s >> i) & 1):
for j in range(n):
# 检查原子 j 是否存在,且不与 i 相同
if i != j and not ((s >> j) & 1):
# i 与 j 碰撞,j 消失
next_s = s | (1 << j)
dp[next_s] = max(dp[next_s], dp[s] + energy[i][j])
max_energy = 0
for s in range(1 << n):
if bin(s).count('1') == n - 1:
max_energy = max(max_energy, dp[s])
print(max_energy)
算法及复杂度
- 算法:状态压缩动态规划
- 时间复杂度:状态总数为
。对于每个状态,我们需要两层循环来枚举所有可能的碰撞原子对
,这需要
的时间。因此,总时间复杂度为
。
- 空间复杂度:我们需要一个
数组来存储所有状态的能量值,其大小为
。因此,空间复杂度为
。