POJ #1789 Truck History 最小生成树(MST) prim 稠密图 链式向前星

Posted 大峰子的博客

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了POJ #1789 Truck History 最小生成树(MST) prim 稠密图 链式向前星相关的知识,希望对你有一定的参考价值。

Description


 

  题目:链接

  这道题的数据集网上比较少,提供一组自己手写的数据:

INPUT:
3
aaaaaaa
baaaaaa
abaaaaa

OUTPUT:
The highest possible quality is 1/2.

 

思路


 

  题意比较不好理解,简而言之就是有 n 个字符串,设两个字符串之间的差异为 dis,dis 由两个字符串对应位置上不同字母的数量决定。比如串A“aaaaaaa” 、串B"baaaaaa" 和串C“abaaaaa”的 dis 分别为 dis(A, B) = 1, dis(A, C) = 1, dis(B, C) = 2 ...etc ,规定除其中一个串外其他串都是由该串派生的,那么产生三个计划:

  1、A派生B、C

  2、B派生A、C

  3、C派生A、B 

  问所有计划中 dis 总和最小的计划是哪一个,输出 dis 总和。

  我们若以每个串为顶点,本题实质上就是求最小生成树的所有边权之和。

  很明显,建出的图是完全图,为稠密图,适合 prim 求解最小生成树。

  TLE N 次... 最后利用 char[] 代替 string 、堆优化prim、 链式向前星(静态邻接表,数组模拟链表)代替vector G[] ,最终1500+ms 险过。但是发现,其实动态申请效率也还可以,关键问题是出在更新d数组时重复入队过多,我(WTF???)

  当然解决 TLE 过程中也收获了很多,比如知道了堆优化的 prim 算法在处理稀疏图问题时居然比 kruskal 更快; 已知数据上界的情况下最好选用 char[] 当作string 和 链式向前星建图,因为vector动态申请非常耗时,push_back() 是成倍申请的,而 string 可能封装得过多了???(我猜的

  关于大牛对链式向前星的理解在这:链接

  测试证明:char[] 代替 string 少了 300+ms ,可怕...

  

  优化前版本

技术分享图片
#include<iostream>
#include<algorithm>
#include<queue>
#include<cstring>
#include<vector>
using namespace std;
#define INF 0x3f3f3f3f
const int MAXN = 2000 + 10;
int n;
string str[MAXN];

int getDis (int u, int v) {
    int cnt = 0;
    for (int i = 0; i <= 6; i++) {
        if (str[u][i] != str[v][i])
            cnt++;
    }
    return cnt;
}

struct Edge {
    int to;
    int dis;
    Edge(int tt, int dd) : to(tt), dis(dd) {}
};
vector<Edge> G[MAXN];

void addEdge (int u, int v) {
    G[u].push_back (Edge(v, getDis(u,v)));
}

void initG() {
    for (int i = 1; i <= n; i++) {
        G[i].clear();
        G[i].reserve(MAXN);
    }
    //建立完全图
    for (int i = 1; i <= n; i++) {
        for (int j = i+1; j <= n; j++) {
            addEdge(i, j);
            addEdge(j, i);
        }
    }
}

struct Node {
    int id;
    int key;
    Node (int v, int kk) : id(v), key(kk) {}
    friend bool operator < (const Node& a, const Node& b) {
        return a.key > b.key;
    }
};
int d[MAXN]; //连接v和树中结点的最小边的权值
bool vis[MAXN]; //v加入树
void prim (const int& s, int& sum ) {
    for (int i = 1; i <= n; i++) d[i] = INF, vis[i] = false;
    d[s] = 0;
    priority_queue<Node> pQueue;
    pQueue.push (Node(s, d[s]));
    while (!pQueue.empty()) {
        int u = pQueue.top().id; pQueue.pop();
        if (vis[u]) continue;
        vis[u] = true;
        sum += d[u]; //MST所有边的权重之和
        for (int j = 0; j < G[u].size(); j++) {
            int v = G[u][j].to;
            if (!vis[v] && d[v] > G[u][j].dis) {
                d[v] = G[u][j].dis;
                pQueue.push(Node(v, d[v]));
            }
        }
    }
}

int main(void) {
    while (cin >> n && n) {
        for (int i = 1; i <= n; i++) cin >> str[i];
        initG();
        int ans = 0;
        prim (1, ans);
        cout << "The highest possible quality is 1/" << ans << "." << endl;
    }
    return 0;
}
View Code

 

  优化后版本

技术分享图片
#include<iostream>
#include<algorithm>
#include<queue>
#include<cstring>
#include<cstdio>
using namespace std;
#define INF 0x3f3f3f3f
const int MAXN = 2010;
const int EDGE_MAXN = 2010*2010;
int n;
char str[MAXN][7];

//求边权
int getDis (int u, int v) {
    int count = 0;
    for (int i = 0; i <= 6; i++) {
        if (str[u][i] != str[v][i]) count++;
    }
    return count;
}

//链式前向星(静态邻接表)
struct Edge {
    int to, dis, next;
}e[EDGE_MAXN];
int head[MAXN], cnt; //head存储以i为起点的最后一条边在e中的位置

void addEdge (int u, int v, int w) {
    cnt++; //从e[1]开始存边
    e[cnt].to = v;
    e[cnt].dis = w;
    e[cnt].next = head[u]; //next存储以u为起点的上一条边在e中的位置
    head[u] = cnt;
}

void initG() {
    memset(head, -1, sizeof(head));
    cnt = 0; //统计边数
    //建立完全图
    for (int i = 1; i <= n; i++) {
        for (int j = i+1; j <= n; j++) {
            int w = getDis(i, j);
            addEdge (i, j, w);
            addEdge (j, i, w);
        }
    }
}

//堆优化prim
struct Node {
    int id;
    int key;
    Node (int v, int kk) : id(v), key(kk) {}
    friend bool operator < (const Node& a, const Node& b) {
        return a.key > b.key;
    }
};
int d[MAXN]; //连接v和树中结点的最小边的权值
bool vis[MAXN]; //判断v是否加入树
void prim (const int& s, int& sum ) {
    memset(d, INF, sizeof(d));
    memset (vis, false, sizeof(vis));
    d[s] = 0;
    priority_queue<Node> pQueue;
    pQueue.push (Node(s, d[s]));
    while (!pQueue.empty()) {
        int u = pQueue.top().id; pQueue.pop();
        if (vis[u]) continue; //已加入树,不必再入树,避免TLE
        vis[u] = true;
        sum += d[u]; //MST所有边的权重之和
        //访问u的所有邻接点
        for (int i = head[u]; i != -1; i = e[i].next) {
            int v = e[i].to;
            if (!vis[v] && d[v] > e[i].dis) {
                d[v] = e[i].dis;
                pQueue.push(Node(v, d[v])); //和dijkstra一样存在重复入队的情况
            }
            /*
            if (!vis[v] ) {
                //d[v] = min (d[v], e[i].dis); 如此松驰将导致即使不更新d数组也入队,造成TLE
                pQueue.push(Node(v, d[v]));
            }
            */
        }
    }
}

int main(void) {
    while (cin >> n && n) {
        for (int i = 1; i <= n; i++) scanf("%s", str[i]);
        initG();
        int ans = 0;
        prim (1, ans);
        cout << "The highest possible quality is 1/" << ans << "." << endl;
    }
    return 0;
}
View Code

 

  prim 和 dijkstra 还真的很像,就是松弛不一样而已。

 

以上是关于POJ #1789 Truck History 最小生成树(MST) prim 稠密图 链式向前星的主要内容,如果未能解决你的问题,请参考以下文章

POJ 1789 Truck History

POJ——T1789 Truck History

POJ - 1789(Truck History)最小生成树

POJ 1789 -- Truck History(Prim)

POJ-1789-Truck History

POJ 1789-Truck History