目录
NOIP 2017 宝藏
题目描述
输入描述:
输出描述:
输入
输出
说明
输入
输出
说明
备注:
代码实现:
NOIP 2017 宝藏
时间限制:C/C++ 1秒,其他语言2秒
空间限制:C/C++ 262144K,其他语言524288K
64bit IO Format: %lld
题目描述
参与考古挖掘的小明得到了一份藏宝图,藏宝图上标出了 n 个深埋在地下的宝藏屋,也给出了这 n 个宝藏屋之间可供开发的 m 条道路和它们的长度。
小明决心亲自前往挖掘所有宝藏屋中的宝藏。但是,每个宝藏屋距离地面都很远,也就是说,从地面打通一条到某个宝藏屋的道路是很困难的,而开发宝藏屋之间的道路则相对容易很多。
小明的决心感动了考古挖掘的赞助商, 赞助商决定免费赞助他打通一条从地面到某个宝藏屋的通道,通往哪个宝藏屋则由小明来决定。
在此基础上, 小明还需要考虑如何开凿宝藏屋之间的道路。已经开凿出的道路可以任意通行不消耗代价。每开凿出一条新道路,小明就会与考古队一起挖掘出由该条道路所能到达的宝藏屋的宝藏。另外,小明不想开发无用道路,即两个已经被挖掘过的宝藏屋之间的道路无需再开发。
新开发一条道路的代价是:
这条道路的长度 x 从赞助商帮你打通的宝藏屋到这条道路起点的宝藏屋所经过的宝藏屋的数量(包括赞助商帮你打通的宝藏屋和这条道路起点的宝藏屋)。
请你编写程序为小明选定由赞助商打通的宝藏屋和之后开凿的道路,使得工程总代价最小,并输出这个最小值。
输入描述:
第一行两个用空格分离的正整数 n 和 m,代表宝藏屋的个数和道路数。 接下来 m 行,每行三个用空格分离的正整数,分别是由一条道路连接的两个宝藏屋的编号(编号为 1~n),和这条道路的长度 v。
输出描述:
输出共一行,一个正整数,表示最小的总代价。
示例1
输入
复制4 5 1 2 1 1 3 3 1 4 1 2 3 4 3 4 1
4 5 1 2 1 1 3 3 1 4 1 2 3 4 3 4 1
输出
复制4
4
说明
示例2
输入
复制4 5 1 2 1 1 3 3 1 4 1 2 3 4 3 4 2
4 5 1 2 1 1 3 3 1 4 1 2 3 4 3 4 2
输出
复制5
5
说明
备注:
对于 20% 的数据:保证输入是一棵树, 1≤ n≤8, v≤ 5000 且所有的 v 都相等。 对于 40% 的数据:1≤ n≤ 8, 0≤ m≤ 1000, v≤ 5000 且所有的 v 都相等。 对于 70% 的数据:1≤ n≤ 8, 0≤ m≤ 1000, v≤ 5000。 对于 100% 的数据:1≤ n≤ 12, 0≤ m≤ 1000, v≤ 500000。
思路解析:
看到数据点n<=12,并且已经选择过的两个任意相邻宝藏点之间的房屋不可再开辟新的道路,可以看出是状压dp。
然后这题比较妙的是因为我们并不知道当前这个点的开发线路是当前线路的第几个点(可能有多种情况可以选择的开发方案)我们并不知道这些方案中那些方案对于以后的抉择是最优秀的,我们只能确定的是他在当前的抉择可能是最优秀的。所以我们枚举这些点的所有开发可能性,有些可能性可能不存在则countinue掉,但是这样枚举可能会使某些状态进行大量无效解的计算,但是一定会包含最优解。又因为枚举可能性是线性的只是会让整体时间复杂度有一个常数级的倍增是可以接受的。
代码实现:
import java.io.*;
import java.math.BigInteger;
import java.util.Arrays;
import java.util.StringTokenizer;
/**
* @ProjectName: study3
* @FileName: Ex36
* @author:HWJ
* @Data: 2023/11/14 12:01
*/
public class Main {
static PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out));
static Input input = new Input(System.in);
static BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
public static void main(String[] args) {
int n = input.nextInt();
int m = input.nextInt();
int[][] map = new int[n][n];
int inf = 1000000000;
for (int i = 0; i < n; i++) {
Arrays.fill(map[i], inf);
map[i][i] = 0;
}
for (int i = 0; i < m; i++) {
int x = input.nextInt() - 1;
int y = input.nextInt() - 1;
int val = input.nextInt();
map[x][y] = Math.min(map[x][y], val);
map[y][x] = Math.min(map[y][x], val);
}
long ans = Long.MAX_VALUE;
long[][] dp = new long[n][1 << n]; // dp[i][st]表示当前状态st在第i层的最小花费数。
for (int i = 0; i < n; i++) {
Arrays.fill(dp[i], inf);
}
for (int i = 0; i < n; i++) {
dp[0][1 << i] = 0;
}
for (int i = 1; i < 1 << n; i++) {
for (int j = (i - 1) & i; j > 0; j = (j - 1) & i) {
int point = j ^ i;
long sum = 0;
for (int x = 0; x < n; x++) {
int min = inf;
if (((1 << x) & point) == 0) continue;
for (int y = 0; y < n; y++) {
if (((1 << y) & j) == 0) continue;
min = Math.min(min, map[x][y]);
}
sum+=min;
}
if (sum >= inf) continue;
for (int k = 1; k < n; k++) {
dp[k][i] = Math.min(dp[k][i], dp[k - 1][j] + k * sum);
}
}
}
for (int i = 0; i < n; i++) {
ans = Math.min(ans, dp[i][(1 << n) - 1]);
}
System.out.println(ans);
}
static class Input {
public BufferedReader reader;
public StringTokenizer tokenizer;
public Input(InputStream stream) {
reader = new BufferedReader(new InputStreamReader(stream), 32768);
tokenizer = null;
}
public String next() {
while (tokenizer == null || !tokenizer.hasMoreTokens()) {
try {
tokenizer = new StringTokenizer(reader.readLine());
} catch (IOException e) {
throw new RuntimeException(e);
}
}
return tokenizer.nextToken();
}
public String nextLine() {
String str = null;
try {
str = reader.readLine();
} catch (IOException e) {
// TODO 自动生成的 catch 块
e.printStackTrace();
}
return str;
}
public int nextInt() {
return Integer.parseInt(next());
}
public long nextLong() {
return Long.parseLong(next());
}
public Double nextDouble() {
return Double.parseDouble(next());
}
public BigInteger nextBigInteger() {
return new BigInteger(next());
}
}
}