·题目链接:https://vjudge.net/problem/ZOJ-3946
题目大意:有N个点,编号0到N-1,有M条可选的双向边,边有长度和花费。在保证以0为起点的单源最短路的前提下,使选择的边的花费最小。
一眼过去想到的是 对最短路上的边求最小生成树。
感觉挺对的,写了很长时间wa了两发后队友指出了发现了错误。
首先,最短路选边之后是一个单起点DAG图(把原图中的双向边拆成2条单向边),最小生成树是按双向边考虑的,但实际上边是单向的,所以并不能保证0号点到任意点可达。
正确做法:
一:直接dij里修改判断条件。
二:遍历最短路上的边,记录某个点的入边的最小花费,然后求和。因为这是一个单起点DAG,所以这样做最后一定可达。
#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a);i <= (b); i++)
#define mp make_pair
#define pb push_back
#define ll long long
#define pli pair<long long,int>
#define all(x) (x).begin(),(x).end()
using namespace std;
const int N = 1e5+10000;
struct node{
int u, v, nxt;
ll len, c;
node(){}
node(int a,int b,ll c1,ll d,int k) {
u = a; v = b; len = c1; c = d; nxt = k;
}
};
int tot=0;
ll dis[N];
int head[3*N];
ll f[N];
node edge[3*N];
vector<node> M_edge;
void add_edge(int u, int v, ll len, ll c) {
tot++;
edge[tot] = node(u,v,len,c,head[u]);
head[u] = tot;
}
void dij(int u) {
dis[u] = 0;
priority_queue<pli> q;
q.push(mp(-dis[u],u));
while(!q.empty()) {
pli x = q.top();
q.pop();
int u = x.second;
for(int i = head[u]; i != -1; i = edge[i].nxt) {
int v = edge[i].v;
ll w = edge[i].len;
if(dis[v]>dis[u]+w) {
dis[v] = dis[u]+w;
q.push(mp(-dis[v],v));
}
}
}
}
int main() {
// freopen("a.txt","r",stdin);
ios::sync_with_stdio(0);
int T;
cin>>T;
while(T--) {
int n, m;
cin>>n>>m;
tot = 0;
memset(head,-1,sizeof(head));
rep(i, 1, m) {
int u, v;
ll len, c;
cin>>u>>v>>len>>c;
add_edge(u,v,len,c);
add_edge(v,u,len,c);
}
rep(i, 0, n-1) dis[i] = 1e18;
dij(0);
ll sum=0;
rep(i, 0, n-1) sum += dis[i];
M_edge.clear();
rep(i, 1, tot) {
int u = edge[i].u;
int v = edge[i].v;
if(dis[v]-dis[u] ==edge[i].len) M_edge.pb(edge[i]);
}
ll cost=0;
rep(i, 0, n-1) f[i] = 1e9;
f[0] = 0;
for(auto x:M_edge) {
int v = x.v;
f[v] = min(f[v],x.c);
}
rep(i, 0, n-1) cost += f[i];
cout<<sum<<' '<<cost<<endl;
}
return 0;
}