最小生成树

最小生成树算法(无向图)

POJ 1679 题目链接

题意

判断最小生成树是否唯一

思路

(1)暴力删除每一条边,判断剩下的边得到的最小生成树权值是否和之前的相等
(2)也可以求次最小生成树,判断与最小生成树是否相等(dp[u][v] = min(dp[u][v的儿子]),dp[u的儿子][v])

#include <cstdio>
#include <string>
#include <algorithm>
#include <vector>
#include <cstring>
using namespace std;
const int N = 1e4 + 5;
const int M = 111;
const int INF = 0x3f3f3f3f;
struct node{
    int u,v,len;
    bool operator < (const node& p) const{
        return len < p.len;
    }
}e[N];
vector<int> g[M];
int dp[M][M];
int cost[M][M];
int pre[M];
int vis[M][M];
int n,m;
int Find(int x)
{
    if (pre[x] == x)    return x;
    return pre[x] = Find(pre[x]);
}
int kruskal()
{
    sort(e, e + m);
    int ans = 0;
    for (int i = 0; i < m; i++){
        int x = Find(e[i].u);
        int y = Find(e[i].v);
        if (x != y){
            pre[x] = y;
            ans += e[i].len;
            g[e[i].v].push_back(e[i].u);
            g[e[i].u].push_back(e[i].v);
            vis[e[i].u][e[i].v] = vis[e[i].v][e[i].u] = 1;
        }
    }
    return ans;
}
int dfs(int root,int u,int fa)
{
    int ans = INF;
    for (int i = 0; i < (int)g[u].size(); i++){
        int v = g[u][i];
        if (v != fa){
            int cur = dfs(root,v,u);
            ans = min(ans,cur);
            dp[u][v] = dp[v][u] = min(cur,dp[u][v]);
        }
    }
    if (root != fa) ans = min(ans,cost[root][u]);
    return ans;
}
void init()
{
    for (int i = 0; i <= n; i++){
        pre[i] = i;
        g[i].clear();
    }
    memset(dp,0x3f,sizeof(dp));
    memset(cost,0x3f,sizeof(cost));
    memset(vis,0,sizeof(vis));
}
int main ()
{
    int t;
    scanf("%d",&t);
    while (t--){
        scanf("%d %d",&n,&m);
        init();
        for (int i = 0; i < m; i++){
            scanf("%d %d %d",&e[i].u,&e[i].v,&e[i].len);
            cost[e[i].u][e[i].v] = cost[e[i].v][e[i].u] = e[i].len;
        }

        int mst = kruskal();

        for (int i = 1; i <= n; i++)    dfs(i, i, -1);

        int ans = INF;
        for (int i = 0; i < m; i++){
            if (vis[e[i].u][e[i].v])
            ans = min(ans,mst - e[i].len + dp[e[i].u][e[i].v]);
        }
        if (ans == mst) printf("Not Unique!\n");
        else printf("%d\n",mst);
    }
	return 0;
}

POJ 3026 题目链接

题意

在一个y行 x列的迷宫中,有可行走的通路空格’ ‘,不可行走的墙’#’,还有两种英文字母A和S,现在从S出发,要求用最短的路径L连接所有字母,输出这条路径L的总长度。

思路

用BFS求出所有点之间的距离(我总觉得2500个点的话可能会超时,可能我的时间复杂度学的不过关)
然后跑一遍prim算法即可
(无语:要在scanf后面加个\n才能A)

#include <cstring>
#include <iostream>
#include <string>
#include <algorithm>
#include <cstdio>
#include <vector>
#include <queue>
#include <cmath>
using namespace std;
const int N = 111;
const int M = 3333;
const int INF = 0x3f3f3f3f;
struct node{
    int to,len,nex;
}e[M * M];
struct point{
    int x,y,len;
};
int head[M],cnt;
int id[N][N];
bool vis[N][N];
bool book[M];
int lowcost[M];
int n,m,num;
int to[4][2] = {1,0,-1,0,0,1,0,-1};
void add(int u,int v,int len)
{
    e[cnt].len = len;
    e[cnt].to = v;
    e[cnt].nex = head[u];
    head[u] = cnt++;
}
void bfs(int x,int y)
{
    queue<point> q;
    q.push({x,y,0});
    memset(vis,false,sizeof(vis));
    vis[x][y] = true;
    while(!q.empty()){
        point tem,now = q.front();
        q.pop();
        for (int i = 0; i < 4; i++){
            tem.x = now.x + to[i][0];
            tem.y = now.y + to[i][1];
            if (tem.x < 0 || tem.x >= n || tem.y < 0 || tem.y >= m) continue;
            if (id[tem.x][tem.y] == 0 || vis[tem.x][tem.y]) continue;
            vis[tem.x][tem.y] = true;
            tem.len = now.len + 1;
            q.push(tem);
            if (id[tem.x][tem.y] > 0) add(id[x][y],id[tem.x][tem.y],tem.len);
        }
    }
}
void prim()
{
    for (int i = head[1]; i != -1; i = e[i].nex){
        int v = e[i].to;
        lowcost[v] = e[i].len;
    }
    int ans = 0;
    memset(book,false,sizeof(book));
    book[1] = true;
    for (int i = 2; i <= num; i++){
        int mi = INF,pos;
        for (int j = 1; j <= num; j++){
            if (!book[j] && lowcost[j] < mi){
                mi = lowcost[j];
                pos = j;
            }
        }
        ans += lowcost[pos];
        book[pos] = true;
        for (int j = head[pos]; j != -1; j = e[j].nex){
            int v = e[j].to;
            if (book[v]) continue;
            lowcost[v] = min(lowcost[v],e[j].len);
        }
    }
    printf("%d\n",ans);
}
int main()
{
    int t;
    scanf("%d",&t);
    while(t--){
        scanf("%d %d\n",&m,&n);
        memset(id,0,sizeof(id));
        num = 0;
        for (int i = 0; i < n; i++){
            getchar();
            for (int j = 0; j < m; j++){
                char ch;
                scanf("%c",&ch);
                if (ch == 'A' || ch == 'S'){
                    id[i][j] = ++num;
                }
                else if (ch != '#') id[i][j] = -1;
            }
        }
        memset(head,-1,sizeof(head));
        cnt = 0;
        for (int i = 0; i < n; i++){
            for (int j = 0; j < m; j++){
                if (id[i][j] > 0) bfs(i,j);
            }
        }
        prim();
    }
    return 0;
}

POJ 1639 题目链接(K度树)

题意

有n个人要去Park聚会。但是Park的停车场是比较小。只能停k辆车。现在问你在这个限制条件下。所有人都到达Park的最短距离

思路
K度树模板题:

如果把那个条件去掉。那么就是就是求最小生成树。加上那个条件其实就是求顶点度数限制为k的最小生成树。做法

1.将该点(以下用v0表示)从图中删除,将得到m个连通分量。
2.对每个连通分量求最小生成树。
3.从每个连通分量中找与v0关联的权值最小的边,与v0相连接,这样将得到v0的最小m度生成树
4.如果 k < m 那么这种树是不存在的。
5.如果 k >= m ,那么考虑构建 m+1度最小生成树,即加入每一条与v0相连的且不在当前的树中的边。

(1)先dp预处理出当前生成树中与顶点V0到Vi的路径中与V0不关联的权值最大的边。dp[i].len记录的是权值,dp[i].u和dp[i].v记录的是边两边的点。

(2)对于每一个不在生成树的<v0,v>把他们加进去最小生成树中,那么就会生成一个环。我们把环中的最大的权值删去。即1中得到的dp[v].len。就会形成m+1个度

(3)对于(2)中我们枚举每一个顶点v。当minnum=min(g[v0][v]-dp[v].d)最小的时候。点v就是所求的点。 (4)重复步骤(3)直到k。其实当某一步的minnum>=0的时候就可以停止算法了。因为就算在增加度数生成树的权值也不会减小

#include <iostream>
#include <cstdio>
#include <cstring>
#include <string>
#include <map>
#include <algorithm>
using namespace std;
const int N = 22;
const int INF = 0x3f3f3f3f;
struct node{
    int u,v,len;
    void Node(int a,int b,int c) {u = a,v = b,len = c;}
    bool operator < (const node& p) const{
        return len < p.len;
    }
}e[N * N],dp[N];       /// dp[i]  表示点 i 与生成树中(非根节点)的最大边
int pre[N];
bool tree[N][N]; ///tree[i][j]=true表示<i, j>这条边在最小生成树中
int min_e[N];
int g[N][N];
int n,m,ans,cnt,k;
map<string,int> mp;
int Find(int x)
{
    return pre[x] == x ? x : (pre[x] = Find(pre[x]));
}
void kruskal()
{
    sort(e, e + n);
    for (int i = 0; i < n; i++){
        if (e[i].u == 1 || e[i].v == 1) continue;
        int px = Find(e[i].u);
        int py = Find(e[i].v);
        if (px != py){
            pre[px] = py;
            tree[e[i].u][e[i].v] = tree[e[i].v][e[i].u] = 1;
            ans += e[i].len;
        }
    }
}
void dfs(int u,int fa)
{
    for (int i = 2; i < cnt; i++){
        if (i == fa || !tree[u][i]) continue;
        if (dp[i].len == -1){
            if (dp[u].len > g[u][i])    dp[i] = dp[u];
            else { 
                dp[i].u = u;
                dp[i].v = i;
                dp[i].len = g[u][i];
            }
        }
        dfs(i,u);
    }
}
void solve()
{
    int vis[N];
    for (int i = 2; i < cnt; i++){
        if (g[1][i] != INF){
            int x = Find(i);
      ///      printf("x = %d\n",x);
            if (g[1][i] < min_e[x]){
                min_e[x] = g[1][i];
                vis[x] = i;
            }
        }
    }
    for (int i = 1; i < cnt; i++){
        if (min_e[i] != INF){
            m++;
            ans += g[1][vis[i]];
            tree[1][vis[i]] = tree[vis[i]][1] = 1;
        }
    }
   /// printf("m = %d ans = %d\n",m,ans);
    for (int i = m + 1; i <= k; i++){
        memset(dp,-1,sizeof(dp));
        dp[1].len = -INF;
        for (int j = 2; j < cnt; j++){
            if (tree[1][j]) dp[j].len = -INF;
        }
        dfs(1,-1);
        int id,mi = INF;
        for (int j = 2; j < cnt; j++){
         ///  printf("g[1][%d] = %d dp[%d].len = %d\n",j,g[1][j],j,dp[j].len);
            if (g[1][j] - dp[j].len < mi){
                mi = g[1][j] - dp[j].len;
                id = j;
            }
        }
        if (mi >= 0)    break;
      ///  printf("mi = %d\n",mi);
        ans += mi;
        tree[1][id] = tree[id][1] = 1;
        tree[dp[id].u][dp[id].v] = tree[dp[id].v][dp[id].u] = 0;
    }
}
void init()
{
    memset(g,0x3f,sizeof(g));
    memset(min_e,0x3f,sizeof(min_e));
    cnt = 1;
    mp["Park"] = cnt++;
    for (int i = 0; i < N; i++) pre[i] = i;
}
int main()
{
    scanf("%d",&n);
    init(); 
    for (int i = 0; i < n; i++){
        string a,b;
        int len;
        cin >> a >> b >> len;
        if (!mp[a]) mp[a] = cnt++;
        if (!mp[b]) mp[b] = cnt++;
        int u = mp[a],v = mp[b];
        e[i].Node(u,v,len);
        g[u][v] = g[v][u] = min(g[u][v],len);
    }
    scanf("%d",&k);
    kruskal();
    solve();
	printf("Total miles driven: %d\n", ans);
    return 0;
}

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值