• [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
    ", 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
    ", ans);
        return 0;
    }
    
  • 相关阅读:
    JavaBean 与 EJB 的区别
    MFC选项卡的实现
    MFC的图片按钮
    windows 下使用 MinGW + msys 编译 ffmpeg
    在windows使用vs2008编译live555
    C89 和 C99 标准比较
    11.求二元查找树的镜像[MirrorOfBST]
    10.排序数组中和为给定值的两个数字[Find2NumbersWithGivenSum]
    9.链表中倒数第k个结点[FindReverseKthLinkedListNode]
    8.另类方法求1+2+...+n[AnotherMethodOfCalculateSumN]
  • 原文地址:https://www.cnblogs.com/wyxwyx/p/noip201722.html
Copyright © 2020-2023  润新知