Acwing 3628 边的删减
题目:
给定一个由 n 个点和 m 条边组成的无向连通加权图。
设点 1 到点 i 的最短路径长度为 di。
现在,你需要删掉图中的一些边,使得图中最多保留 k 条边。
如果在删边操作全部完成后,点 1 到点 i 的最短路径长度仍为 di,则称点 i 是一个优秀点。
你的目标是通过合理进行删边操作,使得优秀点的数量尽可能大。
输入格式
第一行包含三个整数 n,m,k。
接下来 m 行,每行包含三个整数 x,y,w,表示点 x 和点 y 之间存在一条长度为 w 的边。
保证给定无向连通图无重边和自环。
输出格式
第一行包含一个整数 e,表示保留的边的数量 (0≤e≤k)。
第二行包含 e 个不同的 1∼m 之间的整数,表示所保留的边的编号。
按输入顺序,所有边的编号从 1 到 m。
你提供的方案,应使得优秀点的数量尽可能大。
如果答案不唯一,则输出任意满足条件的合理方案均可。
数据范围
对于前五个测试点,2≤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≤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
tag
最短路树,dijkstra,dfs
题意:
无向连通加权图最多保留 k 条边后,点 1 到点 i 的最短路径长度仍为 di,则称点 i 是一个优秀点。通过合理进行删边操作,使得优秀点的数量尽可能大。输出保留的边的数量和保留的边的编号。
思路:
最短路树根据每个点可以由哪个点更新来建立一颗根节点为1号点的树,如果一个点可以由多个点更新则只保留一条边即可。只要保留最短路树里的所有边,所有点的最短路都不变,最短路树外的所有边都是没有意义的。
dijkstra算法每次更新到起点距离最短的点的最短路长度,对于每轮更新出最短路的点来说,最后一次更新他距离的边一定是有价值的,这些有价值的边,连成的点就构成了一个最短路树。
最短路树一个性质:叶子节点都是由其父节点更新而来,因此我们可以直接从根节点开始找到联通的k条边即可!
因此先用 dijkstra算法求出最短路,在用dfs从根节点开始找到联通的k条边。如果dist[b]=dist[a]+w
说明这是一条最短路上的边。输出任意满足条件的合理方案均可。
需要注意的是,最短路长度要用ll
代码:
#include<iostream> #include<algorithm> #include<string> #include<cstring> #include<string.h> #include<vector> #include<stack> #include<queue> #include<map> #include<set> #include<cmath> #define x first #define y second using namespace std;
typedef long long ll;//最短路长度用ll
typedef pair<ll, int> PII;
const int N = 100010, M = 200010;//点最大1e5,无向边则边数最大2e5int n, m, k;
int h[N], e[M], w[M], id[M], ne[M], idx;//dijkstra邻接表
ll dist[N];
bool st[N];
vector<int> ans;void add(int a, int b, int c, int d) // 添加一条边a->b,边权为c
{
e[idx] = b, w[idx] = c, id[idx] = d, ne[idx] = h[a], h[a] = idx ++ ;
}void dijkstra() // 求1号点到n号点的最短路距离
{
memset(dist, 0x3f, sizeof dist);
dist[1] = 0;
priority_queue<PII, vector<PII>, greater<PII>> heap;
heap.push({0, 1});
while (heap.size()){
auto t = heap.top();
heap.pop();
int ver = t.second, 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; i = ne[i])
{
int j = e[i];
if (!st[j] && dist[j] == dist[u] + w[i])
{
if (ans.size() < k){//还没有存多余k个边
ans.push_back(id[i]);//答案存入
}
dfs(j);
}
}
}
int main()
{
cin >> n >> m >> k;
memset(h, -1, sizeof h);//清空邻接表表头
for (int i = 1; i <= m; i ++ )//读入边
{
int a, b, c;
cin >> a >> b >> c;
add(a, b, c, i);//加边
add(b, a, c, i);//加边
}
dijkstra();
memset(st, 0, sizeof st);//保证每个点只遍历一次
dfs(1);//深搜找连通块
cout << ans.size() << endl;
for (auto x: ans){
cout << x << " ";
}
cout << endl;
return 0;
}