[NOIp2017]宝藏
Description
Luogu3959
在一张图中选一个点,并构建一棵生成树,使得生成树上的点到该点的路径权值乘以路径边数的总和最小。
Solution
这个题70分的算法还是比较好想的(然而我还是没想出来),就是一个暴力枚举挖开的点全排列,再枚举这个点是由哪个点挖过来的,就可以轻松拿到70分。
然而满分算法也是暴力...只是加了一个最优性剪枝就可以AC这道题。
UPD:这个题的关键不在最优性剪枝,而是在枚举上dfs传的参数等其他剪枝(因为我把tmp
删了还是能过...)
Code
#include <cstdio>
#include <algorithm>
const int INF = 1917483645;
const int N = 20;
int vis[N], dis[N], d[N];
// vis 当前已经访问过的节点 dis 当前节点到起点的路径边数 d 该节点的出度
int c[N][N], tar[N][N];
// c 邻接矩阵存图 tar 某个点可以到达的点(加速枚举)
int ans = INF, tmp, tot, cnt, n, m, p;
// ans 答案 tmp 最优性剪枝用 tot 当前计算出的答案 n,m 同题意 p 排序辅助变量
/*
注:这里的tmp仅仅是一个估计的最小花费,
实际上并不一定必须按照这份代码的写法。
因为我把tmp去了还是可以过。
所以如果不明白为什么要这样改tmp就不用纠结了,tmp只能是锦上添花。雪中送炭的是num和node
*/
inline bool cmp(int a, int b) { return c[p][a] < c[p][b]; }
void dfs(int num, int node) {
// num 当前起点枚举到的位置 node 当前终点枚举到的位置
// 当且仅当node枚举到d[num]的时候,num才增加,因为这时num的所有出边都已经被枚举过了
for (int i = num; i <= cnt; ++i) {
int &from = vis[i];
if (tot + tmp * dis[from] >= ans) return;
for (int j = node; j <= d[from]; ++j) if (!dis[tar[from][j]]) {
int &to = tar[from][j];
// 更新数据
++cnt;
vis[cnt] = to;
tmp -= c[to][tar[to][1]];
tot += c[from][to] * dis[from];
dis[to] = dis[from] + 1;
// 搜索
dfs(i, j+1);
// 回溯
tot -= c[from][to] * dis[from];
dis[to] = 0;
tmp += c[to][tar[to][1]];
--cnt;
}
node = 1;
}
if (cnt == n) { // 终止条件 放在函数开头结尾应该都行
if (tot < ans) ans = tot;
return;
}
}
int main() {
int u, v, w;
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; ++i)
for (int j = 1; j <= n; ++j)
c[i][j] = INF;
for (int i = 1; i <= m; ++i) {
scanf("%d%d%d", &u, &v, &w);
if (w > c[u][v]) continue;
if (c[u][v] == INF) // 首次加边要更新出度
tar[u][++d[u]] = v, tar[v][++d[v]] = u;
c[u][v] = c[v][u] = w;
}
for (int i = 1; i <= n; ++i) {
p = i;
std::sort(tar[i] + 1, tar[i] + d[i] + 1, cmp);
tmp += c[i][tar[i][1]]; // 统计最小花费
}
for (int i = 1; i <= n; ++i) {
tot = 0; cnt = 1;
vis[1] = i;
tmp -= c[i][tar[i][1]];
dis[i] = 1;
dfs(1, 1);
dis[i] = 0;
tmp += c[i][tar[i][1]];
}
printf("%d\n", ans);
return 0;
}
另外一个好理解一点的剪枝方案:
#include <cstdio>
#include <algorithm>
const int INF = 1917483645;
const int N = 20;
int vis[N], dis[N], d[N];
int c[N][N], tar[N][N];
int ans = INF, tot, cnt, n, m, p;
void dfs(int num, int node) {
for (int i = num; i <= cnt; ++i) {
int &from = vis[i];
if (tot >= ans) return;
for (int j = node; j <= d[from]; ++j) if (!dis[tar[from][j]]) {
int &to = tar[from][j];
++cnt;
vis[cnt] = to;
tot += c[from][to] * dis[from];
dis[to] = dis[from] + 1;
dfs(i, j+1);
tot -= c[from][to] * dis[from];
dis[to] = 0;
--cnt;
}
node = 1;
}
if (cnt == n) {
if (tot < ans) ans = tot;
return;
}
}
int main() {
int u, v, w;
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; ++i)
for (int j = 1; j <= n; ++j)
c[i][j] = INF;
for (int i = 1; i <= m; ++i) {
scanf("%d%d%d", &u, &v, &w);
if (w > c[u][v]) continue;
if (c[u][v] == INF)
tar[u][++d[u]] = v, tar[v][++d[v]] = u;
c[u][v] = c[v][u] = w;
}
for (int i = 1; i <= n; ++i) {
tot = 0; cnt = 1;
vis[1] = i;
dis[i] = 1;
dfs(1, 1);
dis[i] = 0;
}
printf("%d\n", ans);
return 0;
}