[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;
}
posted @ 2018-09-10 16:05  wyxwyx  阅读(98)  评论(0编辑  收藏  举报