JOI 公園 (JOI Park)

原题链接:AT2434 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;
}
posted @ 2022-03-26 10:19  Aphrosia  阅读(36)  评论(0编辑  收藏  举报