NOIP2013货车运输

题目来源https://www.luogu.org/problem/show?pid=1967

 

60分

Kruskal+SPFA

先用Kruskal构造出最大生成树,再在树上跑SPFA,过掉60%的数据。

 

#include <bits/stdc++.h>

using namespace std;
const int maxn = 50001;
int r[maxn], p[maxn], u[maxn], v[maxn], w[maxn];

bool cmp(const int i, const int j) { return w[i] > w[j]; }

int find(int x) {
    if (p[x] == x)return x;
    return p[x] = find(p[x]);
}

const int maxm = 1001;
struct edge {
    int l, t;
};
vector <edge> a[maxn];
bool inq[maxm];
int dis[maxm][maxm];
const int inf = 1000000000;

int main() {
    ios::sync_with_stdio(false);
    int n, m;
    cin >> n >> m;
    for (int i = 1; i <= m; i++) {
        int x, y, z;
        cin >> x >> y >> z;
        u[i] = x;
        v[i] = y;
        w[i] = z;
    }
    for (int i = 1; i <= n; i++)p[i] = i;
    for (int i = 1; i <= m; i++)r[i] = i;
    sort(r + 1, r + 1 + m, cmp);
    for (int i = 1; i <= m; i++) {
        int e = r[i];
        int x = find(v[e]);
        int y = find(u[e]);
        if (x != y) {
            p[x] = y;
            edge r;
            r.t = y;
            r.l = w[e];
            a[x].push_back(r);
            r.t = x;
            a[y].push_back(r);
        }
    }
    int q;
    cin >> q;
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= n; j++) {
            dis[i][j] = inf;
        }
    }
    for (int k = 1; k <= q; k++) {
        int x, y;
        cin >> x >> y;
        if (dis[x][y] < inf) {
            cout << dis[x][y] << endl;
            continue;
        }
        if (dis[y][x] < inf) {
            cout << dis[y][x] << endl;
            continue;
        }
        int dist[maxm];
        for (int i = 1; i <= n; i++)dist[i] = inf;
        queue<int> Q;
        memset(inq, 0, sizeof(inq));
        Q.push(x);
        inq[x] = 1;
        while (!Q.empty()) {
            int e = Q.front();
            Q.pop();
            for (int i = 0; i < a[e].size(); i++) {
                if (a[e][i].t == x)continue;
                if (dist[a[e][i].t] == inf) {
                    dist[a[e][i].t] = min(dist[e], a[e][i].l);
                    if (!inq[a[e][i].t]) {
                        inq[a[e][i].t] = 1;
                        Q.push(a[e][i].t);
                    }
                } else if (dist[a[e][i].t] < min(dist[e], a[e][i].l)) {
                    dist[a[e][i].t] = min(dist[e], a[e][i].l);
                    if (!inq[a[e][i].t]) {
                        inq[a[e][i].t] = 1;
                        Q.push(a[e][i].t);
                    }
                }
            }
            inq[e] = 0;
        }
        for (int i = 1; i <= n; i++) {
            if (dist[i] == inf)dis[x][i] = -1;
            else dis[x][i] = dist[i];
        }
        cout << dis[x][y] << endl;
    }
    return 0;
}

 

100分

Kruskal+LCA

倍增思想。

anc[i][k] 表示从节点i向上走2^k步所到达的节点。

更新:anc[x][i] = anc[anc[x][i - 1]][i - 1]

lcs[i][k] 表示从节点i向上走 2 ^ k 步的过程中经过的权值最小的边。

更新:lcs[x][i] = min(lcs[x][i - 1], lcs[anc[x][i - 1]][i - 1]);

在dfs构造anc[i][k]的同时构造lcs[i][k]。

在线查询节点x,y的lca的同时找出其经过的路径之中边权的最小值。

dep数组表示节点的深度,swim函数是将两节点调到同一深度,并记录经过路径的最小权值。

 

100分代码

 

#include <bits/stdc++.h>

using namespace std;
const int maxn = 10001;
const int maxm = 50001;
const int maxh = 21;
const int inf = 1000000000;
int r[maxm], p[maxm], u[maxm], v[maxm], w[maxm];
struct edge {
    int l, t;
};
vector<edge> a[maxn];
int n, m;
int dep[maxn];
int s[maxn];      //stack<int> s;
int anc[maxn][maxh];
int lcs[maxn][maxh];
int wr[maxn];

bool cmp(const int i, const int j) { return w[i] > w[j]; }

int find(int x) {
    if (p[x] == x)return x;
    return p[x] = find(p[x]);
}

void dfs(int root) {
    int top = 0;
    dep[root] = 1;
    for (int i = 0; i < maxh; i++) {
        anc[root][i] = root;
        lcs[root][i] = inf;
    }
    s[++top] = root;
    while (top) {
        int x = s[top--];
        if (x != root)
            for (int i = 1; i < maxh; i++) {
                anc[x][i] = anc[anc[x][i - 1]][i - 1];
                lcs[x][i] = min(lcs[x][i - 1], lcs[anc[x][i - 1]][i - 1]);
            }
        for (int i = 0; i < a[x].size(); i++) {
            int y = a[x][i].t;
            if (y != anc[x][0]) {
                dep[y] = dep[x] + 1;
                anc[y][0] = x;
                lcs[y][0] = a[x][i].l;
                s[++top] = y;
            }
        }
        while (top && a[s[top]].size() == 0)top--;
    }
    return;
}

int swim(int &x, int h) {
    int tot = inf;
    for (int i = 0; h > 0; i++) {
        if (h & 1) {
            tot = min(tot, lcs[x][i]);
            x = anc[x][i];
        }
        h = (h >> 1);
    }
    return tot;
}

int lca(int x, int y) {
    int pos;
    if (dep[x] > dep[y])swap(x, y);
    int tot = swim(y, dep[y] - dep[x]);
    if (x == y)return tot;
    while (1) {
        for (pos = 0; anc[x][pos] != anc[y][pos]; pos++);
        if (pos == 0) {
            tot = min(lcs[y][0], tot);
            tot = min(lcs[x][0], tot);
            return tot;
        }
        tot = min(tot, lcs[x][pos - 1]);
        x = anc[x][pos - 1];
        tot = min(tot, lcs[y][pos - 1]);
        y = anc[y][pos - 1];
    }
    return -1;
}

int main() {
    ios::sync_with_stdio(false);
    cin >> n >> m;
    for (int i = 1; i <= m; i++) {
        int x, y, z;
        cin >> x >> y >> z;
        u[i] = x;
        v[i] = y;
        w[i] = z;
    }


    for (int i = 1; i <= n; i++)p[i] = i;
    for (int i = 1; i <= m; i++)r[i] = i;
    sort(r + 1, r + 1 + m, cmp);
    for (int i = 1; i <= m; i++) {
        int e = r[i];
        int x = find(v[e]);
        int y = find(u[e]);
        if (x != y) {
            p[x] = y;
            edge r;
            r.t = v[e];
            r.l = w[e];
            a[u[e]].push_back(r);
            r.t = u[e];
            a[v[e]].push_back(r);
        }
    }
    int q;
    cin >> q;
    for (int i = 1; i <= q; i++) {
        int x, y;
        cin >> x >> y;
        int ax = find(x);
        int ay = find(y);
        if (anc[ax][0] == 0)dfs(ax);
        if (anc[ay][0] == 0)dfs(ay);
        if (ax != ay)cout << -1 << endl;
        else cout << lca(x, y) << endl;
    }
    return 0;
}

 

 

 

 

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值