[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;
}

转载于:https://www.cnblogs.com/wyxwyx/p/noip201722.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值