生成树

最小生成树

稀疏图用prim+heap,稠密图用kruscal,一般给的是稀疏图

prim+heap模板:

#include <queue>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <map>
#include <string>
#include <cmath>
#include <iomanip>
#include <algorithm>

using namespace std;
const int N = 2000+10;
const int inf = 0x3f3f3f3f;
int n, m;
double dis[N];
int vis[N];
struct E {
    int to, next;
    double w;
}edge[N*N];

int tot, head[N];

inline void add_edge(int u, int v, double w) {
    edge[tot].to = v;
    edge[tot].w = w;
    edge[tot].next = head[u];
    head[u] = tot++;
}

bool Prim_heap()
{
    int st = 1;
    for (int i = 0; i <= n; ++ i) dis[i] = inf;
    memset(vis, 0, sizeof(vis));
    priority_queue<pair<double,int> > q;
    dis[st] = 0;
    q.push({0,st});
    while (!q.empty()) {
        int u = q.top().second;
        q.pop();
        vis[u] = 1;
        for (int i = head[u]; ~i; i = edge[i].next) {
            int v = edge[i].to;
            double w = edge[i].w;
            if (!vis[v] && dis[v] > w) {
                dis[v] = w;
                q.push(make_pair(-dis[v], v));
                //优先队列大根堆变小根堆小 骚操作:只需一个‘-’号;
            }
        }
    }
    for (int i = 1; i <= n; ++ i)
        if (dis[i] == inf)
            return 0;  //判断是否不存在最小生成树
    return 1;
}

void init()
{
    tot = 0;
    //memset(head, -1, sizeof (head));
    for (int i = 0; i <= n; ++ i) head[i] = -1;
}

int main()
{
    int T;
    scanf("%d", &T);
    while (T--) {
        scanf("%d", &n);
        init();
        int x[n+1], y[n+1];
        for (int i = 1; i <= n; ++ i)
            scanf("%d %d", &x[i], &y[i]);
        for (int i = 1; i <= n; ++ i) {
            for (int j = 1; j <= n; ++ j) {
                if (i == j) continue;
                double temp = sqrt((x[i]-x[j])*(x[i]-x[j])+(y[i]-y[j])*(y[i]-y[j]));
                if (temp < 10 || temp > 1000) continue;
                add_edge(i, j, temp*100);
            }
        }
        if (Prim_heap()) {
            double ans = 0;
            for (int i = 1; i <= n; ++ i)
                ans += dis[i];//, cout << dis[i] << endl;
            cout << fixed << setprecision(1) << ans << endl;
        } else cout << "oh!" << endl;

    }
    return 0;
}
View Code

kruscal:

#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cstring>
#include <vector>
#include <cmath>
using namespace std;
const int N = 100000+10, M = 200000+10;

struct Edge{
    int u,v;
    double w;
    bool operator < (const Edge &E)const {
        return w<E.w;
    }
}edge[M];

int fa[N];
int n,m;

int _find(int x)
{
    if(fa[x]==x)return x;
    else return fa[x]=_find(fa[x]);
}

int main()
{
    scanf("%d", &n);
    int cnt=0, ans=0;
    int x[n+1], y[n+1];
    int k;

    for (int i = 1; i <= n; i++) fa[i] = i;

    for (int i = 1; i <= n; i++)
        scanf("%d %d", &x[i], &y[i]);
    scanf("%d", &k);
    for (int i = 1,u,v; i <= k; ++ i) {
        scanf("%d %d", &u, &v);
        fa[_find(u)] = _find(v);
    }
    for (int i = 1; i < n; ++ i) {
        for (int j = i+1; j <= n; ++ j) {
            ++cnt, edge[cnt].u = i, edge[cnt].v = j,
            edge[cnt].w = sqrt((x[i]-x[j])*(x[i]-x[j])+(y[i]-y[j])*(y[i]-y[j]));
        }
    }
    sort(edge+1, edge+cnt+1);
    vector<pair<int, int> > rec;
    for (int i = 1; i <= cnt; i++) {
        int u = _find(edge[i].u);
        int v = _find(edge[i].v);
        //double w = edge[i].w;
        if (u != v) {
            cnt ++;
            fa[u] = v;
            rec.push_back({edge[i].u, edge[i].v});
        }
    }
    for (int i = 0; i < rec.size(); ++ i) {
        cout << rec[i].first << " " << rec[i].second << endl;
    }

    return 0;
}
View Code

次小生成树

(非严格 / 严格)Kruscal + 倍增LCA  O(nlogn+ mlogm)

顺便存个倍增LCA

#include <bits/stdc++.h>
using namespace std;
const int maxn = 500000+10;
struct Edge{
    int to, next;
    int w;
} edge[maxn*2];

int head[maxn], cnt;
int depth[maxn], fa[maxn][19], lg[maxn];

inline void Add_Edge(int u, int v, int w){
    edge[cnt].next = head[u];
    edge[cnt].to = v;
    edge[cnt].w = w;
    head[u] = cnt++;
}

void dfs(int now, int fath) {  //now表示当前节点,fath表示它的父亲节点
    fa[now][0] = fath; depth[now] = depth[fath] + 1;
    for(int i = 1; i <= lg[depth[now]]; ++i)
        fa[now][i] = fa[fa[now][i-1]][i-1]; //这个转移可以说是算法的核心之一
                                    //意思是now的2^i祖先等于now的2^(i-1)祖先的2^(i-1)祖先
                                        //2^i = 2^(i-1) + 2^(i-1)
    for(int i = head[now]; ~i; i = edge[i].next)
        if(edge[i].to != fath) dfs(edge[i].to, now);
}

int LCA(int x, int y) {
    if(depth[x] < depth[y]) //用数学语言来说就是:不妨设x的深度 >= y的深度
        swap(x, y);
    while(depth[x] > depth[y])
        x = fa[x][lg[depth[x]-depth[y]] - 1]; //先跳到同一深度
    if(x == y)  //如果x是y的祖先,那他们的LCA肯定就是x了
        return x;
    for(int k = lg[depth[x]] - 1; k >= 0; --k) //不断向上跳(lg就是之前说的常数优化)
        if(fa[x][k] != fa[y][k])  //因为我们要跳到它们LCA的下面一层,所以它们肯定不相等,如果不相等就跳过去。
            x = fa[x][k], y = fa[y][k];
    return fa[x][0];  //返回父节点
}

void init() {
    memset(head, -1, sizeof(head));
    cnt = 0;
}

int main() {
    int n, m, s; scanf("%d%d%d", &n, &m, &s);
    init();
    for(int i = 1; i <= n-1; ++i) {
        int x, y; scanf("%d%d", &x, &y);
        Add_Edge(x, y, 0), Add_Edge(y, x, 0);
    }
    for(int i = 1; i <= n; ++i) //预先算出log_2(i)+1的值,用的时候直接调用就可以了
        lg[i] = lg[i-1] + (1 << lg[i-1] == i);  //看不懂的可以手推一下
    dfs(s, 0);
    for(int i = 1; i <= m; ++i) {
        int x, y; scanf("%d%d",&x, &y);
        printf("%d\n", LCA(x, y));
    }
    return 0;
}
View Code

 严格次小生成树 题目链接:P4180 [BJWC2010]严格次小生成树

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll linf = 2147483647000000;
const int inf = 0x3f3f3f3f;
const int maxn = 100000+10;
const int maxm = 400000+10;
struct Edge{
    int to, next;
    ll w;
} edge[maxn*2];

struct Tree{
    int to, from;
    ll w;
    bool operator< (const Tree& tt) {return w < tt.w;}
} tree[maxm];

int n, m;
int head[maxn], cnt;
int depth[maxn], fa[maxn][19], lg[maxn], fat[maxn]; //fa用于lca查询, fat用于并查集
ll dis1[maxn][19], dis2[maxn][19];
int used[maxn];

inline ll read()
{
    int x=0;
    char ch=getchar();
    while(ch<'0'||ch>'9')ch=getchar();
    while(ch>='0'&&ch<='9')x=x*10+(ch^48),ch=getchar();
    return x;
}

inline void Add_Edge(int u, int v, ll w){
    edge[cnt].next = head[u], edge[cnt].to = v, edge[cnt].w = w;
    head[u] = cnt++;
//    edge[cnt].next = head[v], edge[cnt].to = u, edge[cnt].w = w;
//    head[v] = cnt++;
}

//因为DFS可能会爆栈。我就没用了

//void dfs(int now, int fath) {  //now表示当前节点,fath表示它的父亲节点
//    fa[now][0] = fath; depth[now] = depth[fath] + 1;
//    for(int i = 1; i <= lg[depth[now]]; ++i)
//        fa[now][i] = fa[fa[now][i-1]][i-1]; //这个转移可以说是算法的核心之一
//                                    //意思是now的2^i祖先等于now的2^(i-1)祖先的2^(i-1)祖先
//                                        //2^i = 2^(i-1) + 2^(i-1)
//    for(int i = head[now]; ~i; i = edge[i].next)
//        if(edge[i].to != fath) dfs(edge[i].to, now);
//}

void bfs() {
    queue<int> q; q.push(1), depth[1] = 1;
    while (!q.empty()) {
        int fath = q.front(); q.pop();
        for(int i = head[fath]; ~i; i = edge[i].next) {
            int now = edge[i].to;
            if (depth[now]) continue;
            fa[now][0] = fath, depth[now] = depth[fath] + 1;
            dis1[now][0] = edge[i].w, dis2[now][0] = -linf;
            q.push(now);
            for(int j = 1; j <= lg[depth[now]]; ++j) {
                fa[now][j] = fa[fa[now][j-1]][j-1];
                //最大值肯定是max不用管
                dis1[now][j] = max(dis1[now][j-1], dis1[fa[now][j-1]][j-1]);
                //这可真像树形DP,而我树形DP老菜了。。
                //如果这一次的权值与最大权值相同,那么次大值不用更新,直接次大取大即可
                if (dis1[now][j-1] == dis1[fa[now][j-1]][j-1])
                    dis2[now][j] = max(dis2[now][j-1], dis2[fa[now][j-1]][j-1]);
                //如果这一次的权值与比最大权值还大,那么就在更新前的最大和次大值取大
                if (dis1[now][j-1] > dis1[fa[now][j-1]][j-1])
                    dis2[now][j] = max(dis2[now][j-1], dis1[fa[now][j-1]][j-1]);
                //如果这一次的权值比最大权值小,那么就在这次的权值和次大值取大
                if (dis1[now][j-1] < dis1[fa[now][j-1]][j-1])
                    dis2[now][j] = max(dis1[now][j-1],dis2[fa[now][j-1]][j-1]);
            }
        }
    }
}

int LCA(int x, int y) {
    if(depth[x] < depth[y]) //用数学语言来说就是:不妨设x的深度 >= y的深度
        swap(x, y);
    while(depth[x] > depth[y])
        x = fa[x][lg[depth[x]-depth[y]] - 1]; //先跳到同一深度
    if(x == y)  //如果x是y的祖先,那他们的LCA肯定就是x了
        return x;
    for(int k = lg[depth[x]] - 1; k >= 0; --k) //不断向上跳(lg就是之前说的常数优化)
        if(fa[x][k] != fa[y][k])  //因为我们要跳到它们LCA的下面一层,所以它们肯定不相等,如果不相等就跳过去。
            x = fa[x][k], y = fa[y][k];
    return fa[x][0];  //返回父节点
}

int _find(int x) {return fat[x]==x?x:fat[x]=_find(fat[x]);}

ll Kruscal() {
    ll sum = 0;
    sort(tree+1, tree+m+1);
    for (int i = 1; i <= n; ++ i) fat[i] = i;
    for (int i = 1; i <= m; ++ i) {
        int u = tree[i].from, v = tree[i].to;
        ll w = tree[i].w;
        int fx = _find(u), fy = _find(v);
        if (fx != fy) {
            used[i] = 1, sum += tree[i].w, fat[fx] = fy;
            Add_Edge(u, v, w), Add_Edge(v, u, w);
        }
    }
    return sum;
}

void init() {
    memset(head, -1, sizeof(head));
    memset(used, 0, sizeof(used));
    memset(depth, 0, sizeof(depth));
    cnt = 0;
    memset(dis1, 0, sizeof(dis1));
    memset(dis2, 0, sizeof(dis2));
    memset(fa, 0, sizeof(fa));
}

//计算u到v路径上的最大值跟w的差值
ll cal(int u, int v, ll w) {
    ll val1=0, val2=0;
    for (int i = lg[depth[u]]; i >= 0; -- i) {
        //倍增找最大
        if (depth[fa[u][i]] >= depth[v]) {
            if (dis1[u][i] > val1) {
                val2 = val1;
                val1 = dis1[u][i];
            }
            val2  = max(val2, dis2[u][i]);
            u = fa[u][i];
        }
    }
    if (val1 != w) return w - val1;
    return w - val2;
}

ll solve() {
    ll base = Kruscal();
    ll ans = linf;
    bfs();
    for (int i = 1; i <= m; ++ i) {
        if (used[i]) continue;
        int u = tree[i].from, v = tree[i].to;
        int lca = LCA(u, v);
        ll w = tree[i].w;
        ans = min(ans, min(base+cal(u, lca, w), base+cal(v, lca, w)));
    }
    return ans;
}

int main() {
    for(int i = 1; i < maxn; ++i) //预先算出log_2(i)+1的值,用的时候直接调用就可以了
        lg[i] = lg[i-1] + (1 << lg[i-1] == i);  //看不懂的可以手推一下
    scanf("%d%d", &n, &m);
    init();
    for(int i = 1; i <= m; ++i)
        scanf("%d%d%lld", &tree[i].from, &tree[i].to, &tree[i].w);
    cout << solve() << endl;
    return 0;
}
View Code

非严格次小生成树 给个板子题吧:K - The Unique MST  POJ - 1679

#include <cstring>
#include <iostream>
#include <queue>
#include <cstdio>
#include <algorithm>
using namespace std;
typedef long long ll;
const ll linf = 2147483647000000;
const int inf = 0x3f3f3f3f;
const int maxn = 1000+10;
const int maxm = 40000+10;
struct Edge{
    int to, next;
    ll w;
} edge[maxn*2];

struct Tree{
    int to, from;
    ll w;
    bool operator< (const Tree& tt) const {return w < tt.w;}
} tree[maxm];

int n, m;
int head[maxn], cnt;
int depth[maxn], fa[maxn][19], lg[maxn], fat[maxn]; //fa用于lca查询, fat用于并查集
ll dis1[maxn][19];
int used[maxn];

inline ll read()
{
    int x=0;
    char ch=getchar();
    while(ch<'0'||ch>'9')ch=getchar();
    while(ch>='0'&&ch<='9')x=x*10+(ch^48),ch=getchar();
    return x;
}

inline void Add_Edge(int u, int v, ll w){
    edge[cnt].next = head[u], edge[cnt].to = v, edge[cnt].w = w;
    head[u] = cnt++;
//    edge[cnt].next = head[v], edge[cnt].to = u, edge[cnt].w = w;
//    head[v] = cnt++;
}

//因为DFS可能会爆栈。我就没用了

//void dfs(int now, int fath) {  //now表示当前节点,fath表示它的父亲节点
//    fa[now][0] = fath; depth[now] = depth[fath] + 1;
//    for(int i = 1; i <= lg[depth[now]]; ++i)
//        fa[now][i] = fa[fa[now][i-1]][i-1]; //这个转移可以说是算法的核心之一
//                                    //意思是now的2^i祖先等于now的2^(i-1)祖先的2^(i-1)祖先
//                                        //2^i = 2^(i-1) + 2^(i-1)
//    for(int i = head[now]; ~i; i = edge[i].next)
//        if(edge[i].to != fath) dfs(edge[i].to, now);
//}

void bfs() {
    queue<int> q; q.push(1), depth[1] = 1;
    while (!q.empty()) {
        int fath = q.front(); q.pop();
        for(int i = head[fath]; ~i; i = edge[i].next) {
            int now = edge[i].to;
            if (depth[now]) continue;
            fa[now][0] = fath, depth[now] = depth[fath] + 1;
            dis1[now][0] = edge[i].w;
            q.push(now);
            for(int j = 1; j <= lg[depth[now]]; ++j) {
                fa[now][j] = fa[fa[now][j-1]][j-1];
                //最大值肯定是max不用管
                dis1[now][j] = max(dis1[now][j-1], dis1[fa[now][j-1]][j-1]);
            }
        }
    }
}

int LCA(int x, int y) {
    if(depth[x] < depth[y]) //用数学语言来说就是:不妨设x的深度 >= y的深度
        swap(x, y);
    while(depth[x] > depth[y])
        x = fa[x][lg[depth[x]-depth[y]] - 1]; //先跳到同一深度
    if(x == y)  //如果x是y的祖先,那他们的LCA肯定就是x了
        return x;
    for(int k = lg[depth[x]] - 1; k >= 0; --k) //不断向上跳(lg就是之前说的常数优化)
        if(fa[x][k] != fa[y][k])  //因为我们要跳到它们LCA的下面一层,所以它们肯定不相等,如果不相等就跳过去。
            x = fa[x][k], y = fa[y][k];
    return fa[x][0];  //返回父节点
}

int _find(int x) {return fat[x]==x?x:fat[x]=_find(fat[x]);}

ll Kruscal() {
    ll sum = 0;
    sort(tree+1, tree+m+1);
    for (int i = 1; i <= n; ++ i) fat[i] = i;
    for (int i = 1; i <= m; ++ i) {
        int u = tree[i].from, v = tree[i].to;
        ll w = tree[i].w;
        int fx = _find(u), fy = _find(v);
        if (fx != fy) {
            used[i] = 1, sum += tree[i].w, fat[fx] = fy;
            Add_Edge(u, v, w), Add_Edge(v, u, w);
        }
    }
    return sum;
}


//(非严格)计算u到v路径上的最大值跟w的差值
ll cal2(int u, int v) {
    ll val=0;
    for (int i = lg[depth[u]]; i >= 0; -- i) {
        //倍增找最大
        if (depth[fa[u][i]] >= depth[v]) {
            val  = max(val, dis1[u][i]);
            u = fa[u][i];
        }
    }
    return val;
}

ll solve(ll base) {
    ll ans = linf;
    bfs();
    for (int i = 1; i <= m; ++ i) {
        if (used[i]) continue;
        int u = tree[i].from, v = tree[i].to;
        int lca = LCA(u, v);
        ll w = tree[i].w;
        ll maxw = max(cal2(u, lca), cal2(v, lca));
        ans = min(ans, base+w-maxw);
    }
    return ans;
}

void init() {
    memset(head, -1, sizeof(head));
    memset(used, 0, sizeof(used));
    memset(depth, 0, sizeof(depth));
    cnt = 0;
    memset(dis1, 0, sizeof(dis1));
    memset(fa, 0, sizeof(fa));

}

int main() {
    int T;
    scanf("%d", &T);
    for(int i = 1; i < maxn; ++i) //预先算出log_2(i)+1的值,用的时候直接调用就可以了
        lg[i] = lg[i-1] + (1 << lg[i-1] == i);  //看不懂的可以手推一下
    while (T--) {
        scanf("%d%d", &n, &m);
        init();
        for(int i = 1; i <= m; ++i)
            scanf("%d%d%lld", &tree[i].from, &tree[i].to, &tree[i].w);
        ll base = Kruscal();
        ll ans = solve(base);
        //cout << ans << " " << base << endl;
        if (ans > base) cout << base << endl;
        else cout << "Not Unique!" << endl;
    }

    return 0;
}
View Code

B - Qin Shi Huang's National Road System  HDU - 4081

这道题目比较毒瘤吧。虽然就是考一个很简单的非严格次小生成树的点。

只需要枚举每一条边,之后如果这条边在MST上那么直接边两端val相加除以mst-w即可

否则就是将u和v的路径上最大的那条边代替w

我采用上面的方式一直WA,改用rec记录最大值之后就A了。真是离谱

//#include <bits/stdc++.h>
#include <queue>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <iostream>
#include <cstdlib>
using namespace std;
typedef long long ll;
const ll linf = 2147483647000000;
const int inf = 0x3f3f3f3f;
const int maxn = 1000+10;
const int maxm = maxn*maxn+10;
struct Edge{
    int to, next;
    double w;
} edge[maxn*2];

struct Tree{
    int to, from;
    double w;
    bool operator< (const Tree& tt) {return w<tt.w;}
} tree[maxm];

int n, m;
int head[maxn], cnt;
int depth[maxn], fa[maxn][19], lg[maxn], fat[maxn]; //fa用于lca查询, fat用于并查集
double dis1[maxn][19];
int used[maxn];
double xx[maxn], yy[maxn], val[maxn];
double rec[maxn][maxn];
int cntt;

inline void Add_Edge(int u, int v, double w){
    edge[cnt].next = head[u], edge[cnt].to = v, edge[cnt].w = w;
    head[u] = cnt++;
}

void bfs() {
    queue<int> q; q.push(1), depth[1] = 1;
    while (!q.empty()) {
        int fath = q.front(); q.pop();
        for(int i = head[fath]; ~i; i = edge[i].next) {
            int now = edge[i].to;
            if (depth[now]) continue;
            fa[now][0] = fath, depth[now] = depth[fath] + 1;
            dis1[now][0] = edge[i].w;
            q.push(now);
            for(int j = 1; j <= lg[depth[now]]; ++j) {
                fa[now][j] = fa[fa[now][j-1]][j-1];
                dis1[now][j] = max(dis1[now][j-1], dis1[fa[now][j-1]][j-1]);
            }
        }
    }
}

int LCA(int x, int y) {
    if(depth[x] < depth[y]) //用数学语言来说就是:不妨设x的深度 >= y的深度
        swap(x, y);
    while(depth[x] > depth[y])
        x = fa[x][lg[depth[x]-depth[y]] - 1]; //先跳到同一深度
    if(x == y)  //如果x是y的祖先,那他们的LCA肯定就是x了
        return x;
    for(int k = lg[depth[x]] - 1; k >= 0; --k) //不断向上跳(lg就是之前说的常数优化)
        if(fa[x][k] != fa[y][k])  //因为我们要跳到它们LCA的下面一层,所以它们肯定不相等,如果不相等就跳过去。
            x = fa[x][k], y = fa[y][k];
    return fa[x][0];  //返回父节点
}

int _find(int x) {return fat[x]==x?x:fat[x]=_find(fat[x]);}

double Kruscal() {
    double sum = 0;
    sort(tree+1, tree+cntt+1);
    for (int i = 1; i <= n; ++ i) fat[i] = i;
    for (int i = 1; i <= cntt; ++ i) {
        int u = tree[i].from, v = tree[i].to;
        double w = tree[i].w;
        int fx = _find(u), fy = _find(v);
        if (fx != fy) {
            used[i] = 1, sum += tree[i].w, fat[fx] = fy;
            Add_Edge(u, v, w), Add_Edge(v, u, w);
        }
    }
    return sum;
}

void init() {
    memset(head, -1, sizeof(head));
    memset(used, 0, sizeof(used));
    memset(depth, 0, sizeof(depth));
    memset(fa, 0, sizeof(fa));
    memset(dis1, 0, sizeof(dis1));
    cnt = cntt = 0;
}

//(非严格)计算u到v路径上的最大值跟w的差值
double cal2(int u, int v) {
    double val=0;
    for (int i = lg[depth[u]]; i >= 0; -- i) {
        //倍增找最大
        if (depth[fa[u][i]] >= depth[v]) {
            val  = max(val, dis1[u][i]);
            u = fa[u][i];
        }
    }
    return val;
}

double solve(double base) {
    double ans = 0;
    bfs();
    for (int i = 1; i < n; ++ i) {
        for (int j = 1; j <= n; ++ j) {
            int lca = LCA(i, j);
            rec[i][j] = rec[j][i] = max(cal2(i, lca), cal2(j, lca));
        }
    }
    for (int i = 1; i < n; i++) {
        for (int j = i+1; j <= n; j++) {
            double v = base - rec[i][j];
            v = (val[i] + val[j]) / v;
            ans = max(ans, v);
        }
    }
    return ans;
}
int main() {
//    freopen("in.txt","r",stdin); //输入重定向,输入数据将从in.txt文件中读取
//    freopen("out1.txt","w",stdout); //输出重定向,输出数据将保存在out.txt文件中
    int T; scanf("%d", &T);
    for(int i = 1; i <= maxn; ++i) //预先算出log_2(i)+1的值,用的时候直接调用就可以了
        lg[i] = lg[i-1] + (1 << lg[i-1] == i);  //看不懂的可以手推一下
    while (T--) {
        scanf("%d", &n);
        init();
        for(int i = 1; i <= n; ++i)
            scanf("%lf%lf%lf", &xx[i], &yy[i], &val[i]);
        for (int i = 1; i < n; ++ i) {
            for (int j = i+1; j <= n; ++ j) {
                double w = sqrt((xx[i]-xx[j])*(xx[i]-xx[j])+(yy[i]-yy[j])*(yy[i]-yy[j]));
                tree[++cntt].from = i, tree[cntt].to = j, tree[cntt].w = w;
                //cout << i << " " << j << " " << w << endl;
            }
        }
        double base = Kruscal();
        double ans = solve(base);
        printf("%.2f\n", ans);
    }
    return 0;
}
View Code

D - Is There A Second Way Left? UVA - 10462

 这几道非严格最小生成树让我成功意识到了我的初始化不够完善。

这道题就是判断有没有最小生成树和次小生成树。

#include <cstring>
#include <iostream>
#include <queue>
#include <cstdio>
#include <algorithm>
using namespace std;
typedef long long ll;
const ll linf = 2147483647000000;
const int inf = 0x3f3f3f3f;
const int maxn = 200+20;
const int maxm = 40000+10;
struct Edge{
    int to, next;
    ll w;
} edge[maxn*2];

struct Tree{
    int to, from;
    ll w;
    bool operator< (const Tree& tt) const {return w < tt.w;}
} tree[maxm];

int n, m;
int head[maxn], cnt;
int depth[maxn], fa[maxn][19], lg[maxn], fat[maxn]; //fa用于lca查询, fat用于并查集
ll dis1[maxn][19];
int used[maxn];
int cntt;

inline void Add_Edge(int u, int v, ll w){
    edge[cnt].next = head[u], edge[cnt].to = v, edge[cnt].w = w;
    head[u] = cnt++;
}

void bfs() {
    queue<int> q; q.push(1), depth[1] = 1;
    while (!q.empty()) {
        int fath = q.front(); q.pop();
        for(int i = head[fath]; ~i; i = edge[i].next) {
            int now = edge[i].to;
            if (depth[now]) continue;
            fa[now][0] = fath, depth[now] = depth[fath] + 1;
            dis1[now][0] = edge[i].w;
            q.push(now);
            for(int j = 1; j <= lg[depth[now]]; ++j) {
                fa[now][j] = fa[fa[now][j-1]][j-1];
                dis1[now][j] = max(dis1[now][j-1], dis1[fa[now][j-1]][j-1]);
            }
        }
    }
}

int LCA(int x, int y) {
    if(depth[x] < depth[y]) swap(x, y);
    while(depth[x] > depth[y]) x = fa[x][lg[depth[x]-depth[y]] - 1];
    if(x == y) return x;
    for(int k = lg[depth[x]] - 1; k >= 0; --k)
        if(fa[x][k] != fa[y][k])
            x = fa[x][k], y = fa[y][k];
    return fa[x][0];
}

int _find(int x) {return fat[x]==x?x:fat[x]=_find(fat[x]);}

ll Kruscal() {
    ll sum = 0;
    sort(tree+1, tree+m+1);
    for (int i = 1; i <= n; ++ i) fat[i] = i;
    for (int i = 1; i <= m; ++ i) {
        int u = tree[i].from, v = tree[i].to;
        ll w = tree[i].w;
        int fx = _find(u), fy = _find(v);
        if (fx != fy) {
            used[i] = 1, sum += tree[i].w, fat[fx] = fy;
            Add_Edge(u, v, w), Add_Edge(v, u, w);
            cntt++;
        }
    }
    return sum;
}

void init() {
    memset(head, -1, sizeof(head));
    memset(used, 0, sizeof(used));
    memset(depth, 0, sizeof(depth));
    cnt = cntt = 0;
    memset(dis1, 0, sizeof(dis1));
    memset(fa, 0, sizeof(fa));

}

//(非严格)计算u到v路径上的最大值
ll cal2(int u, int v) {
    ll val=0;
    for (int i = lg[depth[u]]; i >= 0; -- i) {
        //倍增找最大
        if (depth[fa[u][i]] >= depth[v]) {
            val  = max(val, dis1[u][i]);
            u = fa[u][i];
        }
    }
    return val;
}

ll solve(ll base) {
    ll ans = linf;
    bfs();
    for (int i = 1; i <= m; ++ i) {
        if (used[i]) continue;
        int u = tree[i].from, v = tree[i].to;
        int lca = LCA(u, v);
        ll w = tree[i].w;
        ans = min(ans, base+w-max(cal2(u,lca),cal2(v,lca)));
    }
    return ans;
}

int main() {
//    freopen("in.txt","r",stdin); //输入重定向,输入数据将从in.txt文件中读取
//    freopen("out1.txt","w",stdout); //输出重定向,输出数据将保存在out.txt文件中
    int T;
    scanf("%d", &T);
    for(int i = 1; i < maxn; ++i) //预先算出log_2(i)+1的值,用的时候直接调用就可以了
        lg[i] = lg[i-1] + (1 << lg[i-1] == i);  //看不懂的可以手推一下
    for (int Ti = 1; Ti <= T; ++ Ti) {
        scanf("%d%d", &n, &m);
        init();
        for(int i = 1; i <= m; ++i)
            scanf("%d%d%lld", &tree[i].from, &tree[i].to, &tree[i].w);
        ll base = Kruscal();
        cout << "Case #" << Ti << " : ";
        if (cntt != n-1) {
            cout << "No way" << endl;
            continue;
        }
        if (m == n-1) {
            cout << "No second way" << endl;
            continue;
        }
        cout << solve(base) << endl;
    }

    return 0;
}
View Code

最小树形图

算法流程

对于每一次循环:

  1. 贪心找出所谓的“最小带环树形图”,(就是上面的万能图的红边)
    顺便记录一下自己是从哪里来的(入边的起点)
  2. 把所有选出的边加到ans里面
  3. 找环记录环,统计数量
  4. 如果没环,代表完成了,退出循环
  5. 把所有不是环上的点全部设置为自己是一个独立环(大小为1的新环)
  6. 重新设置边权&缩点
  7. 完成缩点,重新设置n和root,然后初始

附一个朱刘算法 O(VE)

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
using namespace std;
typedef long long ll;
const int maxn = 1000+50;
const int maxm = 50000+50;
const int inf = 0x3f3f3f3f;
inline int read() {
    int a=0;char c=getchar();
    while(c<'0'||c>'9') c=getchar();
    while(c>='0'&&c<='9') a=(a<<1)+(a<<3)+c-'0',c=getchar();
    return a;
}
struct edge{int u,v;ll w;} e[maxm];
int cnt,fa[maxn],scc[maxn],top[maxn];
ll minw[maxn];
//cnt当前图环的数量
//scc[u]代表u节点在第scc[u]个环中
//top[u]代表u所在链的代表元素 类似并查集
//minw[u]为当前连到u点的最短边的边权 fa[v]当前连到v点的最短边的u

inline ll zhuliu(int r, int n, int m) {
    ll ans = 0;
    while(1) {
        for (int i = 1; i <= n; ++i) minw[i] = inf;
        for (int i = 1; i <= m; ++i)
            if (e[i].u != e[i].v && e[i].w < minw[e[i].v]) {//不是自环 并且边权比选定的还小
                fa[e[i].v] = e[i].u, minw[e[i].v] = e[i].w;
            }
        int cnt = 0, u;
        minw[r] = 0;
        for (int i = 1; i <= n; ++ i) {
            scc[i] = top[i] = 0;
            if (minw[i] == inf) return -1;
        }

        for (int i = 1; i <= n; ++i) {
            ans += minw[i];
            for (u = i; u != r && top[u] != i && !scc[u]; u = fa[u]) top[u]=i; //找环 //找到包含不在环中的点最多的链 打上标记
            if(u != r && !scc[u]) { //这时候还满足条件说明vis[u]==i 即成环,将在环里的点打上环的编号
                scc[u] = ++cnt;
                for (int v = fa[u]; v != u; v = fa[v]) scc[v] = cnt;
            }
        }
        if (!cnt) return ans; //没环就是找到答案了
        for (int i = 1; i <= n; ++i) if (!scc[i]) scc[i] = ++cnt;//i节点不存在当前树中 就给他自己成一个环

        for (int i = 1; i <= m; ++i) {
            ll last = minw[e[i].v]; //last等于当前连进v点的边的最小权值
            if ((e[i].u=scc[e[i].u]) != (e[i].v=scc[e[i].v])) e[i].w -= last; //当前边的两个端点不在同一个环内
        }
        n = cnt, r = scc[r];//缩完点后 当前点数就为环数 根节点就是根节点所在的环
    }
}

int main() {
    int T;
    scanf("%d", &T);
    for (int Ti = 1; Ti <= T; Ti++) {
        int n, m;
        scanf("%d%d", &n, &m);
        for(int i = 1,u,v,w; i <= m; i++) {
            scanf("%d%d%lld", &u, &v, &w);
            e[i] = {u+1,v+1,w};
        }
        printf("Case #%d: ", Ti);
        ll ans = zhuliu(1,n,m);
        if (ans != -1) printf("%lld\n", ans);
        else printf("Possums!\n");
    }
    return 0;
}
View Code

效率更好的便是用tajan优化的。O(m+nlogn)

tajan优化主要在于找最小入边。使用左偏树和按秩合并并查集优化处理 (虽然我不是很懂)。

存个酷酷的tajan板子 不过这个常数比较大的样子。如果复杂度够的话还是用朱刘吧。

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#define il inline
#define ri register
#define Size 100050
using namespace std;
int fa[Size],cnt,is[Size];
il int find(int);
il void read(int&),Union(int,int);
struct leftist{
    struct point{
        int l,r,d,v,t,to;
    }a[Size]={{0,0,-1,0,0,0}};
    int r[Size];
    il void merge(int&x,int&y){
        if(!x||!y){x^=y;return;}
        if(a[x].v>a[y].v)swap(x,y);
        a[y].t-=a[x].t,a[y].v-=a[x].t;
        merge(a[x].r,y);
        if(a[a[x].l].d<a[a[x].r].d)
            a[x].l^=a[x].r^=a[x].l^=a[x].r;
        a[x].d=a[a[x].r].d+1;
    }
    il void spread(int&p){
        a[a[p].l].t+=a[p].t,a[a[p].r].t+=a[p].t;
        a[a[p].l].v+=a[p].t,a[a[p].r].v+=a[p].t;
        a[p].t=0;
    }
    il void pop(int&x){
        spread(x),merge(a[x].l,a[x].r),x=a[x].l;
    }
    il point*top(int&x){
        while(r[x]&&!(find(a[r[x]].to)^x))pop(r[x]);
        if(!r[x])puts("-1"),exit(0);
        a[r[x]].to=find(a[r[x]].to);
        return &a[r[x]];
    }
}L;
int pre[Size];
int main(){
    int n,m,r,ans(0);leftist::point*temp;
    read(n),read(m),read(r),cnt=n,is[r]=r;
    for(int i(1),u,v,w;i<=m;++i)
        read(u),read(v),read(w),
            L.a[i]={0,0,0,w,0,u},
            L.merge(L.r[v],u=i);
    for(int i(1);i<=n<<1;++i)fa[i]=i;
    for(int i(1),j(i);i<=n;j=++i)
        while(!is[j]){
            while(!is[j])
                is[j]=i,j=(temp=L.top(j))->to,
                    ans+=temp->v;if(is[j]^i)break;
            while(~is[j])
                is[j]=-1,j=pre[j]=(temp=L.top(j))->to,
                    temp->t-=temp->v,temp->v=0;++cnt;
            while(is[j]^i)is[j]=i,Union(j,cnt),j=pre[j];
            j=cnt;
        }return printf("%d",ans),0;
}
il void Union(int u,int v){
    if((u=find(u))^(v=find(v)))
        L.merge(L.r[v],L.r[u]),fa[u]=v;
}
il int find(int x){
    return x^fa[x]?fa[x]=find(fa[x]):x;
}
il void read(int&x){
    x^=x;ri char c;while(c=getchar(),c<'0'||c>'9');
    while(c>='0'&&c<='9')x=(x<<1)+(x<<3)+(c^48),c=getchar();
}
View Code

给道裸题吧:E - Command Network POJ - 3164

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
using namespace std;
typedef long long ll;
const int maxn = 200+50;
const int maxm = 50000+50;
const int inf = 1e10;
inline int read() {
    int a=0;char c=getchar();
    while(c<'0'||c>'9') c=getchar();
    while(c>='0'&&c<='9') a=(a<<1)+(a<<3)+c-'0',c=getchar();
    return a;
}
struct edge{int u,v;double w;} e[maxm];
int cnt,fa[maxn],scc[maxn],top[maxn];
double minw[maxn];
//cnt当前图环的数量
//scc[u]代表u节点在第scc[u]个环中
//top[u]代表u所在链的代表元素 类似并查集
//minw[u]为当前连到u点的最短边的边权 fa[v]当前连到v点的最短边的u

double zhuliu(int r, int n, int m) {
    double ans = 0;
    while(1) {
        for (int i = 1; i <= n; ++i) minw[i] = inf;
        for (int i = 1; i <= m; ++i)
            if (e[i].u != e[i].v && e[i].w < minw[e[i].v]) //不是自环 并且边权比选定的还小
                fa[e[i].v] = e[i].u, minw[e[i].v] = e[i].w;

        int u, cnt = 0;
        minw[r] = 0;
        for (int i = 1; i <= n; ++ i) {
            scc[i] = top[i] = 0;
            if (minw[i] == inf) return -1;
        }

        for (int i = 1; i <= n; ++i) {
            ans += minw[i];
            for (u = i; u != r && top[u] != i && !scc[u]; u = fa[u]) top[u]=i; //找环 //找到包含不在环中的点最多的链 打上标记
            if(u != r && !scc[u]) { //这时候还满足条件说明vis[u]==i 即成环,将在环里的点打上环的编号
                scc[u] = ++cnt;
                for (int v = fa[u]; v != u; v = fa[v]) scc[v] = cnt;
            }
        }

        if (!cnt) return ans; //没环就是找到答案了
        for (int i = 1; i <= n; ++i) if (!scc[i]) scc[i] = ++cnt;//i节点不存在当前树中 就给他自己成一个环

        for (int i = 1; i <= m; ++i) {
            double last = minw[e[i].v]; //last等于当前连进v点的边的最小权值
            if ((e[i].u=scc[e[i].u]) != (e[i].v=scc[e[i].v])) e[i].w -= last; //当前边的两个端点不在同一个环内
        }
        n = cnt, r = scc[r];//缩完点后 当前点数就为环数 根节点就是根节点所在的环
    }
}
double dist(double x1, double y1, double x2, double y2){
    return sqrt((x1-x2)*(x1-x2)+(y1-y2)*(y1-y2));
}
int main() {
    int n, m;
    while (scanf("%d%d", &n, &m) != EOF) {
        double x[n+1], y[n+1];
        for (int i = 1; i <= n; i++)
            scanf("%lf%lf", &x[i], &y[i]);
        for(int i = 1,u,v; i <= m; i++) {
            scanf("%d%d", &u, &v);
            e[i] = {u,v,dist(x[u],y[u],x[v],y[v])};
        }
        double ans = zhuliu(1,n,m);
        if (ans != -1) printf("%.2f\n",ans);
        else printf("poor snoopy\n");
    }
    return 0;
}
View Code

 还有道:F - Teen Girl Squad  UVA - 11183

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
using namespace std;
typedef long long ll;
const int maxn = 1000+50;
const int maxm = 50000+50;
const int inf = 0x3f3f3f3f;
inline int read() {
    int a=0;char c=getchar();
    while(c<'0'||c>'9') c=getchar();
    while(c>='0'&&c<='9') a=(a<<1)+(a<<3)+c-'0',c=getchar();
    return a;
}
struct edge{int u,v;ll w;} e[maxm];
int cnt,fa[maxn],scc[maxn],top[maxn];
ll minw[maxn];
//cnt当前图环的数量
//scc[u]代表u节点在第scc[u]个环中
//top[u]代表u所在链的代表元素 类似并查集
//minw[u]为当前连到u点的最短边的边权 fa[v]当前连到v点的最短边的u

inline ll zhuliu(int r, int n, int m) {
    ll ans = 0;
    while(1) {
        for (int i = 1; i <= n; ++i) minw[i] = inf;
        for (int i = 1; i <= m; ++i)
            if (e[i].u != e[i].v && e[i].w < minw[e[i].v]) {//不是自环 并且边权比选定的还小
                fa[e[i].v] = e[i].u, minw[e[i].v] = e[i].w;
            }
        int cnt = 0, u;
        minw[r] = 0;
        for (int i = 1; i <= n; ++ i) {
            scc[i] = top[i] = 0;
            if (minw[i] == inf) return -1;
        }

        for (int i = 1; i <= n; ++i) {
            ans += minw[i];
            for (u = i; u != r && top[u] != i && !scc[u]; u = fa[u]) top[u]=i; //找环 //找到包含不在环中的点最多的链 打上标记
            if(u != r && !scc[u]) { //这时候还满足条件说明vis[u]==i 即成环,将在环里的点打上环的编号
                scc[u] = ++cnt;
                for (int v = fa[u]; v != u; v = fa[v]) scc[v] = cnt;
            }
        }
        if (!cnt) return ans; //没环就是找到答案了
        for (int i = 1; i <= n; ++i) if (!scc[i]) scc[i] = ++cnt;//i节点不存在当前树中 就给他自己成一个环

        for (int i = 1; i <= m; ++i) {
            ll last = minw[e[i].v]; //last等于当前连进v点的边的最小权值
            if ((e[i].u=scc[e[i].u]) != (e[i].v=scc[e[i].v])) e[i].w -= last; //当前边的两个端点不在同一个环内
        }
        n = cnt, r = scc[r];//缩完点后 当前点数就为环数 根节点就是根节点所在的环
    }
}

int main() {
    int T;
    scanf("%d", &T);
    for (int Ti = 1; Ti <= T; Ti++) {
        int n, m;
        scanf("%d%d", &n, &m);
        for(int i = 1,u,v,w; i <= m; i++) {
            scanf("%d%d%lld", &u, &v, &w);
            e[i] = {u+1,v+1,w};
        }
        printf("Case #%d: ", Ti);
        ll ans = zhuliu(1,n,m);
        if (ans != -1) printf("%lld\n", ans);
        else printf("Possums!\n");
    }
    return 0;
}
View Code

之后給道无根最小树形图的题目:G - Ice_cream’s world II HDU - 2121

感谢这道题目让我发现了板子有点点问题,现在已经更正了(板子upupup)

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
using namespace std;
typedef long long ll;
const int maxn = 2000+50;
const int maxm = 50000+50;
const ll inf = 1e17;
int rr; //记录实根位置
ll ans;
inline int read() {
    int a=0;char c=getchar();
    while(c<'0'||c>'9') c=getchar();
    while(c>='0'&&c<='9') a=(a<<1)+(a<<3)+c-'0',c=getchar();
    return a;
}
struct edge{int u,v;ll w;} e[maxm];
int cnt,fa[maxn],scc[maxn],top[maxn];
ll minw[maxn];
//cnt当前图环的数量
//scc[u]代表u节点在第scc[u]个环中
//top[u]代表u所在链的代表元素 类似并查集
//minw[u]为当前连到u点的最短边的边权 fa[v]当前连到v点的最短边的u

inline ll zhuliu(int r, int n, int m) {
    ll ans = 0;
    while(1) {
        for (int i = 1; i <= n; ++i) minw[i] = inf;
        for (int i = 1; i <= m; ++i)
            if (e[i].u != e[i].v && e[i].w < minw[e[i].v]) {//不是自环 并且边权比选定的还小
                fa[e[i].v] = e[i].u, minw[e[i].v] = e[i].w;
                //因为有缩点,每个点的下标发生了变化,但新加的边的指向未变,
                //所以可以通过这个边找到原来点的下标
                if (e[i].u == r) rr = i;
            }
        int cnt = 0, u;
        minw[r] = 0;
        for (int i = 1; i <= n; ++ i) {
            scc[i] = top[i] = 0;
            if (minw[i] == inf) return -1;
        }

        for (int i = 1; i <= n; ++i) {
            ans += minw[i];
            for (u = i; u != r && top[u] != i && !scc[u]; u = fa[u]) top[u]=i; //找环 //找到包含不在环中的点最多的链 打上标记
            if(u != r && !scc[u]) { //这时候还满足条件说明vis[u]==i 即成环,将在环里的点打上环的编号
                scc[u] = ++cnt;
                for (int v = fa[u]; v != u; v = fa[v]) scc[v] = cnt;
            }
        }
        if (!cnt) return ans; //没环就是找到答案了
        for (int i = 1; i <= n; ++i) if (!scc[i]) scc[i] = ++cnt;//i节点不存在当前树中 就给他自己成一个环

        for (int i = 1; i <= m; ++i) {
            ll last = minw[e[i].v]; //last等于当前连进v点的边的最小权值
            if ((e[i].u=scc[e[i].u]) != (e[i].v=scc[e[i].v])) e[i].w -= last; //当前边的两个端点不在同一个环内
        }
        n = cnt, r = scc[r];//缩完点后 当前点数就为环数 根节点就是根节点所在的环
    }
}

int main() {
    int n, m;
    while (~scanf("%d%d", &n, &m)) {
        ll sum = 0;
        for(int i = 1,u,v; i <= m; i++) {
            ll w;
            scanf("%d%d%lld", &u, &v, &w);
            e[i] = {u+1,v+1,w};
            sum += w;
        }
        sum += 1; //设虚根为n+1
        for (int i = 1; i <= n; ++ i)
            e[i+m] = {n+1,i,sum};
        //cout << sum << endl;
        ll ans = zhuliu(n+1,n+1, n+m);
        //cout << ans << endl;
        if (ans == -1 || ans >= (2*sum)) {
                //cout << ans << " " << 2*sum << endl;
                printf("impossible\n\n"); //不存在
        }
        else {
            printf("%lld %d\n\n", ans-sum, rr-m-1) ;
        }
    }

    return 0;
}
View Code

 还有一道类似于无根MDST的:H - Transfer water HDU - 4009

这个建点非常酷。

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
using namespace std;
typedef long long ll;
const int maxn = 1000+50;
const int maxm = 1000000+50;
const int inf = 0x3f3f3f3f;
inline int read() {
    int a=0;char c=getchar();
    while(c<'0'||c>'9') c=getchar();
    while(c>='0'&&c<='9') a=(a<<1)+(a<<3)+c-'0',c=getchar();
    return a;
}
struct edge{int u,v;ll w;} e[maxm];
int cnt,fa[maxn],scc[maxn],top[maxn];
ll minw[maxn];
//cnt当前图环的数量
//scc[u]代表u节点在第scc[u]个环中
//top[u]代表u所在链的代表元素 类似并查集
//minw[u]为当前连到u点的最短边的边权 fa[v]当前连到v点的最短边的u

inline ll zhuliu(int r, int n, int m) {
    ll ans = 0;
    while(1) {
        for (int i = 1; i <= n; ++i) minw[i] = inf;
        for (int i = 1; i <= m; ++i)
            if (e[i].u != e[i].v && e[i].w < minw[e[i].v]) {//不是自环 并且边权比选定的还小
                fa[e[i].v] = e[i].u, minw[e[i].v] = e[i].w;
            }
        int cnt = 0, u;
        minw[r] = 0;
        for (int i = 1; i <= n; ++ i) {
            scc[i] = top[i] = 0;
            if (minw[i] == inf) return -1;
        }

        for (int i = 1; i <= n; ++i) {
            ans += minw[i];
            for (u = i; u != r && top[u] != i && !scc[u]; u = fa[u]) top[u]=i; //找环 //找到包含不在环中的点最多的链 打上标记
            if(u != r && !scc[u]) { //这时候还满足条件说明vis[u]==i 即成环,将在环里的点打上环的编号
                scc[u] = ++cnt;
                for (int v = fa[u]; v != u; v = fa[v]) scc[v] = cnt;
            }
        }
        if (!cnt) return ans; //没环就是找到答案了
        for (int i = 1; i <= n; ++i) if (!scc[i]) scc[i] = ++cnt;//i节点不存在当前树中 就给他自己成一个环

        for (int i = 1; i <= m; ++i) {
            ll last = minw[e[i].v]; //last等于当前连进v点的边的最小权值
            if ((e[i].u=scc[e[i].u]) != (e[i].v=scc[e[i].v])) e[i].w -= last; //当前边的两个端点不在同一个环内
        }
        n = cnt, r = scc[r];//缩完点后 当前点数就为环数 根节点就是根节点所在的环
    }
}
int cal(int x1, int y1, int z1, int x2, int y2, int z2) {
    return abs(x1-x2)+abs(y1-y2)+abs(z1-z2);
}
int main() {
    int n,X,Y,Z;
       while(scanf("%d%d%d%d", &n, &X, &Y, &Z) != EOF && (n||X||Y||Z)){
        int x[n+1], y[n+1], z[n+1];
        for (int i = 1; i <= n; ++ i)
            scanf("%d%d%d", x+i, y+i, z+i);
        int m = 0;
        for(int u = 1,num,v; u <= n; ++ u){
            scanf("%d", &num);
            while(num--){
                scanf("%d", &v);
                e[++m].w = cal(x[u],y[u],z[u],x[v],y[v],z[v]) * Y;
                if (z[v] > z[u]) e[m].w += Z;
                e[m].u = u; e[m].v = v;
            }
        }
        for (int i = 1; i <= n; ++i){
            e[++m].u = n+1; e[m].v = i;
            e[m].w = z[i] * X;
        }
        ll ans = zhuliu(n+1,n+1,m);
        printf("%lld\n", ans);
    }
    return 0;
}
View Code
原文地址:https://www.cnblogs.com/Vikyanite/p/13471994.html