题目链接:
Codeforces 240E Road Repairs
题意:
有n个城市,编号1–n,首都编号为1,有m条有向边u[i],v[i],w[i],w[i]=0表示这条边是完好的,w[i]=1表示这条边需要修理,问从首都出发能到达任意城市最少需要修多少条边?如果不能到达任意城市输出-1,否则输出需要修复的最少边数和边的编号。如果有多组答案输出任意一组。
分析:
输出最小树形图的路径。
#include <iostream>
#include <cstdio>
#include <cstring>
#include <string>
#include <algorithm>
#include <climits>
#include <cmath>
#include <ctime>
#include <cassert>
#define IOS ios_base::sync_with_stdio(0); cin.tie(0);
using namespace std;
typedef long long ll;
const int MAX_N = 100010;
const int MAX_M = 2000010;
const int INF = 0x7fffffff;
int n, m, NV, NE;
int pre[MAX_N], vis[MAX_N], In[MAX_N], ID[MAX_N];
int usedEdge[MAX_M], preEdge[MAX_N];
struct Edge{
int u, v, w, ww, id;
Edge() {}
Edge(int _u, int _v, int _w, int _ww, int _id) : u(_u), v(_v), w(_w), ww(_ww), id(_id) {}
}edge[MAX_M];
struct Used{
int pre, id;
}cancle[MAX_M];
int ZLEdmonds(int root)
{
memset(usedEdge, 0, sizeof(usedEdge));
int res = 0, u, v, w;
int total = NE;
while(1){
for(int i = 0; i < NV; i++) { In[i] = INF; }
for(int i = 0; i < NE; i++){
u = edge[i].u, v = edge[i].v, w = edge[i].w;
if(u != v && w < In[v]){
In[v] = w;
pre[v] = u;
//记录这个顶点所在的边编号
preEdge[v] = edge[i].id;
}
}
for(int i = 0; i < NV; i++){
if(i != root && In[i] == INF) return -1;
}
int cnt = 0;
memset(vis, -1, sizeof(vis));
memset(ID, -1, sizeof(ID));
In[root] = 0;
for(int i = 0; i < NV; i++){
res += In[i];
v = i;
if(i != root){ //非根节点
usedEdge[preEdge[i]]++; //这个顶点所在的边被使用
int t = preEdge[i];
}
while(v != root && vis[v] != i && ID[v] == -1){
vis[v] = i;
v = pre[v];
}
if(v != root && ID[v] == -1){
for(u = pre[v]; u != v; u = pre[u]){
ID[u] = cnt;
}
ID[v] = cnt++;
}
}
if(cnt == 0) break;
for(int i = 0; i < NV; i++){
if(ID[i] == -1) ID[i] = cnt++;
}
for(int i = 0; i < NE;i ++){
u = edge[i].u, v = edge[i].v;
edge[i].u = ID[u], edge[i].v = ID[v];
//将原先的边的id重新编号
if(edge[i].u != edge[i].v){
//edge[i].u 和edge[i].v 是新图的点编号,两者不相等说明两者在原图中不属于同一有向环
edge[i].w -= In[v];
//如果在新图中用到该边那么上一次图中用到这条边就要被取消
cancle[total].id = edge[i].id;
cancle[total].pre = preEdge[v];
//重新编排新图的边的编号
edge[i].id = total++;
}
}
NV = cnt;
root = ID[root];
}
for(int i = total - 1; i >= NE; i--){ //从后往前扫新建边编号
if(usedEdge[i]){ //如果这条边被使用
usedEdge[cancle[i].id]++; //这条边的使用情况+1
usedEdge[cancle[i].pre]--; //上一次用到这条边被取消
//相当于在上一次图中的有向环中的这个指向v顶点的有向边被取消
}
}
return res;
}
int main()
{
while(~scanf("%d%d", &n, &m)){
for(int i = 0; i < m; i++){
int u, v, w;
scanf("%d%d%d", &u, &v, &w);
u--, v--;
edge[i] = Edge(u, v, w, w, i);
}
NV = n, NE = m;
int ans = ZLEdmonds(0);
if(ans == -1 || ans == 0) printf("%d\n", ans);
else {
printf("%d\n", ans);
for(int i = 0; i < m; i++){
if(edge[i].ww == 1 && usedEdge[i]) printf("%d ", i + 1);
}
printf("\n");
}
}
return 0;
}