3628. 边的删减(最短路树)

给定一个由 nn 个点和 mm 条边组成的无向连通加权图

设点 11 到点 ii 的最短路径长度为 didi。

现在,你需要删掉图中的一些边,使得图中最多保留 kk 条边。

如果在删边操作全部完成后,点 11 到点 ii 的最短路径长度仍为 didi,则称点 ii 是一个优秀点。

你的目标是通过合理进行删边操作,使得优秀点的数量尽可能大。

输入格式

第一行包含三个整数 n,m,kn,m,k。

接下来 mm 行,每行包含三个整数 x,y,wx,y,w,表示点 xx 和点 yy 之间存在一条长度为 ww 的边。

保证给定无向连通图无重边和自环。

输出格式

第一行包含一个整数 ee,表示保留的边的数量 (0≤e≤k)(0≤e≤k)。

第二行包含 ee 个不同的 1∼m1∼m 之间的整数,表示所保留的边的编号。

按输入顺序,所有边的编号从 11 到 mm。

你提供的方案,应使得优秀点的数量尽可能大。

如果答案不唯一,则输出任意满足条件的合理方案均可。

数据范围

对于前五个测试点,2≤n≤15,1≤m≤152≤n≤15,1≤m≤15。
对于全部测试点,2≤n≤105,1≤m≤105,n−1≤m,0≤k≤m,1≤x,y≤n,x≠y,1≤w≤1092≤n≤105,1≤m≤105,n−1≤m,0≤k≤m,1≤x,y≤n,x≠y,1≤w≤109。

输入样例1:

3 3 2
1 2 1
3 2 1
1 3 3

输出样例1:

2
1 2

输入样例2:

4 5 2
4 1 8
2 4 1
2 1 3
3 4 9
3 1 5

输出样例2:

2
3 2

题目描述 :

本题给定一个无向连通加权图,从第 1 个点出发到第 i 个点的最短距离记为 di

接着我们需要进行操作,删掉图中的一些边,最多保留原图中的 k 条边

删完边后,如果点 i 到点 1 的距离任然是原图的中最短路长度,则我们称该点为优秀点

我们需要找出一种删边方案,使得最终图中有尽可能多的 优秀点

思路: 首先我们要做的肯定是先求得每个点i到1点的最短路,所以我们要选择一个最短路算法。

接着看下一个要求,我们要找出一个保留小于等于 k 条边的方案,使得构成最短路的点尽可能多

为了保证我们保留下来的边是有价值的,因此我们保留的边一定是更新出最短路的边

而 Dijkstra算法Dijkstra算法 的贪心思想是:每次更新到起点距离最短的点的最短路长度

利用该性质,我们知道:
Dijkstra算法Dijkstra算法 对于每轮更新出最短路的点来说,最后一次更新他距离的边一定是 有价值的

这些有价值的边,连成的点就构成了一个最短路树

#include<bits/stdc++.h>
#include<queue>
#include<vector>
#define x first
#define y second

using namespace std;

#define ll long long
#define PII pair<ll,int>
const int N=2e5+5;
ll n,m,k;
ll h[N],e[N],w[N],id[N],ne[N],idx;
//     存后续 权重 边的编号
ll dist[N];
bool st[N];
vector<int>ans;
void add(int a,int b,int c,int d) {
	e[idx]=b;
	w[idx]=c;
	id[idx]=d;//这条边的编号
	ne[idx]=h[a];
	h[a]=idx++;
}
void Dj() {
	memset(dist,0x3f,sizeof(dist));
	dist[1]=0;
	priority_queue<PII,vector<PII>,greater<PII>>heap;
	//大到小
	heap.push({0,1});//长度为0,节点编号为1
	while(heap.size()) {
		auto t=heap.top();
		heap.pop();
		int ver=t.second;
		int distance=t.first;
		if(st[ver])continue;
		st[ver]=true;
		for(int i=h[ver]; i!=-1; i=ne[i]) {
			int j=e[i];
			if(dist[j]>dist[ver]+w[i]) {
				dist[j]=dist[ver]+w[i];
				heap.push({dist[j],j});
			}
		}
	}
}

void dfs(int u) {
	st[u]=true;//这个点没有被连结过
	for(int i=h[u]; i!=-1; i=ne[i]) {
		int j=e[i];//这个点的后续连接点
		if(!st[j]&&dist[j]==dist[u]+w[i]) {
			//这个点没有被遍历过并且他就是最短路
			if(ans.size()<k)ans.push_back(id[i]);
			//如果当前保留边小与k
			dfs(j);
		}
	}
}

int main() {
	scanf("%d%d%d",&n,&m,&k);
	memset(h,-1,sizeof(h));
	for(int i=1; i<=m; i++) {
		int a,b,c;
		scanf("%d%d%d",&a,&b,&c);
		add(a,b,c,i);
		add(b,a,c,i);
		//两个连接点 a b,权重 c 这条边的编号 i

	}
	Dj();
	memset(st, 0, sizeof st);
	dfs(1);//从一号点(起点)开始跑
	cout<<ans.size()<<endl;
	for(auto x:ans) {
		cout<<x<<' ';
	}
	return 0;
}

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值