JOI 公園 (JOI Park)
写完之后看到题解区的三分吓了一跳
分析与解答
由于最终答案与边权有关,所以不妨考虑判断一条边是否会对答案有贡献。
记 \(dis\) 表示以点 \(1\) 为源点的最短路径长度,那么答案可以表示为 \(X \times C + \sum\limits_{i=1}^{m} d_i\ [\max(dis_{a_i}, dis_{b_i}) \gt X]\),也就是说只有当一条道路两端点的最短路长度均大于当前 \(X\) 时,这条道路的才会对答案有贡献。
这一点是可以根据题目描述得到的。
然后注意到一个事实:虽然题目中说 \(X\) 可以取任意自然数,但是显然 \(X\) 取以点 \(1\) 为源点的一条最短路径长度时最优。显然易证·也就是说枚举 \(X\) 的值时只需要枚举 \(dis\) 数组即可。
所以我们只需要求出以点 \(1\) 为源点的最短路径长度,然后求出每一条边想要被计入答案所需要的最小 \(X\) 值,记为 \(val\),即 \(\max(dis_{a_i}, dis_{b_i})\),然后按照该值对边排序,最后按照枚举当前 \(val\) 计算答案即可。
形象地说,就是在对边进行排序只后,选用一条边 \(i\) 作为“分界点”,所有 \(val_j \gt val_i\) 的边 \(j\) 的边权都会计入答案,其他则不会。
记得开 long long
。
这里运用堆优化的 Dijkstra 算法求最短路,时间复杂度 \(\Theta(n \log n)\)
Code
#include <cstdio>
#include <algorithm>
#include <iostream>
#include <cstring>
#include <queue>
using namespace std;
typedef long long ll;
typedef pair<long long,int> pli;
const int MAXN = 100010;
const int MAXM = 200010;
const ll INF = 0x3f3f3f3f3f3f3f3f;
int n, m, c;
struct edge1{
int u, v, w;
ll val; int ind;
bool operator<(const edge1 &o)const{return val < o.val;}
}a[MAXM];
struct edge{
int ne, to, w;
edge(int N=0,int T=0,int W=0):ne(N),to(T),w(W){}
}e[MAXM<<1];
int fir[MAXN], num = 0;
inline void join(int a, int b, int c)
{
e[++num] = edge(fir[a], b, c);
fir[a] = num;
}
ll dis[MAXN];
bool vis[MAXN];
priority_queue<pli,vector<pli>,greater<pli> > h;
inline void dijkstra(int s = 1)
{
for(int i=1;i<=n;i++)
dis[i] = INF, vis[i] = 0;
dis[s] = 0;
h.push(make_pair(dis[s], s));
while(!h.empty())
{
int u = h.top().second;
h.pop();
vis[u] = 1;
for(int i=fir[u];i;i=e[i].ne)
{
int v = e[i].to;
if(dis[v] > dis[u] + e[i].w)
{
dis[v] = dis[u] + e[i].w;
if(!vis[v]) h.push(make_pair(dis[v], v));
}
}
}
}
int main()
{
ll sum = 0, ans = 0;
scanf("%d%d%d",&n,&m,&c);
for(int i=1;i<=m;i++)
{
scanf("%d%d%d",&a[i].u,&a[i].v,&a[i].w);
join(a[i].u, a[i].v, a[i].w);
join(a[i].v, a[i].u, a[i].w);
a[i].ind = i;
sum += a[i].w;
}
ans = sum;
dijkstra(1); // 求最短路
for(int i=1;i<=m;i++)
a[i].val = max(dis[a[i].u], dis[a[i].v]);
sort(a+1, a+m+1);
for(int i=1;i<=m;i++)
{
sum -= a[i].w;
ans = min(ans, sum+1ll*a[i].val*c);
// 枚举以一条边为分界点时的答案
}
printf("%lld\n",ans);
return 0;
}