洛谷

洛谷题单指南-最小生成树-P1194 买礼物

原题链接:https://www.luogu.com.cn/problem/P1194

题意解读:每个物品都有价格a,如果手上有物品i,且i->j有非0权值,那么买只用花该权值也可以买j,求用最少的钱买到所有物品。

解题思路:

方法一、直接思考

考虑如何买每一个物品:

设待买的物品为t

如果t与已经买的物品之间有小于a的权值d,那么只用花费d就可以买到t,否则要花费a

对于已经购买的物品,形成一个集合,如何确定下一个要买的物品?

根据贪心原则,显然下一个物品与集合中物品的权值越小越好,这不就是prim算法吗?

设物品是节点,物品与物品之间权值不为0则建边,为0则不建边或者用邻接矩阵表示就是无穷大。

然后从1开始跑一遍prim,需要注意的是:

1、1号物品必选,dist[1]可以初始化为a

2、将物品选入集合是,价格有两种选择,应该取min(边权值,a)

3、为了避免出现有些点无法连通,在寻找不在集合中距离集合最近的点时判断要用dist[j] <= dist[t],这样才能选出dist[x]=INF的点

100分代码:

#include <bits/stdc++.h>
using namespace std;

const int N = 505, INF = 0x3f3f3f3f;
int g[N][N];
int dist[N];
bool vis[N];
int a, b, ans;

int main()
{
    cin >> a >> b;
    for(int i = 1; i <= b; i++)
        for(int j = 1; j <= b; j++)
        {
            int w;
            cin >> w;
            if(w == 0) g[i][j] = INF; //注意0表示不会优惠,因此价格要设置无穷大,便于比较
            else g[i][j] = w;
        }

    memset(dist, 0x3f, sizeof(dist));
    dist[1] = a; //1号点必选,花费肯定是a,不要设置为0,后面累加花费是会取min
    for(int i = 1; i <= b; i++)
    {
        int t = 0;
        for(int j = 1; j <= b; j++) //找到集合外距离集合最近的点t
            if(!vis[j] && (t == 0 || dist[j] <= dist[t])) //dist[j] <= dist[t]如果集合外的点与集合中的点都不连通的时候,也可以找到一个点
                t = j;
        vis[t] = true;
        ans += min(dist[t], a); //选取t的时候,根据边长和a的大小去较小值
        //用t更新相邻节点的dist
        for(int j = 1; j <= b; j++) 
        {
            dist[j] = min(dist[j], g[t][j]);
        }
    }
    cout << ans;

    return 0;
}

方法二:虚拟礼物

每个礼物要么直接买,要么通过买其他相邻的礼物顺便买,且第一个礼物必须直接买。

可以假设一个虚拟礼物,编号为0,对于其他所有礼物都有K[0][i]=K[i][0]=a,那么在0到其他节点都建立一条长度为a的边。

这样,所有礼物都可以表示成0~b的点,购买所有礼物就是要使得0~b的点都连通。

在整个图中跑一次最小生成树,即可得到购买所有礼物的最小花费。

100分代码:

#include <bits/stdc++.h>
using namespace std;

const int N = 505, INF = 0x3f3f3f3f;
int g[N][N];
int dist[N];
bool vis[N];
int a, b, ans;

int main()
{
    cin >> a >> b;
    for(int i = 1; i <= b; i++)
    {
        g[0][i] = g[i][0] = a; //虚拟0点
        for(int j = 1; j <= b; j++)
        {
            int w;
            cin >> w;
            if(w == 0) g[i][j] = INF; //注意0表示不会优惠,因此价格要设置无穷大,便于比较
            else g[i][j] = w;
        }
    }

    memset(dist, 0x3f, sizeof(dist));
    //正常跑一遍prim
    dist[0] = 0; 
    for(int i = 0; i <= b; i++)
    {
        int t = -1; //从0开始选,初始设为-1
        for(int j = 0; j <= b; j++) //找到集合外距离集合最近的点t
            if(!vis[j] && (t == -1 || dist[j] < dist[t])) 
                t = j;
        vis[t] = true;
        ans += dist[t];
        //用t更新相邻节点的dist
        for(int j = 0; j <= b; j++) 
        {
            dist[j] = min(dist[j], g[t][j]);
        }
    }
    cout << ans;

    return 0;
}