常用图论算法总结,大部分代码摘自网络,个人总结整理,不定期更新
图的存储:
1.vector:方便易用
#include
#include
using namespace std;
int const MAX_M=1000;
struct Edge
{
int to,cost,next;
};
vector G[MAX_M];
void addedge(int u,int v,int c)
{
G[u].push_back((Edge){v,c,(int)G[v].size()});
}
int main()
{
for(int i=1;i<=m;i++) //初始化
G[i].clear();
addedge(a,b,c); //读入边
for(int i=0;i//遍历u连向的所有边
{
Edge &e=G[u][i];
cout<
2.链式前向星:速度快,体积小
#include
#include
using namespace std;
int const MAX_M=100000; //一定要开很大,否则会TLE
int const MAX_N=100000;
struct Edge
{
int to,cost,next;
}e[MAX_M];
int eid,p[MAX_N];
void init()
{
eid=0;
memset(p,-1,sizeof(p));
}
void insert(int u,int v,int c)
{
e[eid].to=v;
e[eid].cost=c;
e[eid].next=p[u];
p[u]=eid++;
}
void addedge(int u,int v,int c)
{
insert(u,v,c);
insert(v,u,0);
}
int main()
{
init() //初始化,每次必须要有!
addedge(a,b,c); //读入边
for(int i=p[u];i!=-1;i=e[i].next) //遍历u连向的所有边
{
cout<"->"<
LCA最近公共祖先:
在线算法-倍增法:O(VlogV+QlogV) Q为查询次数
#include
#include
using namespace std;
const int MAX_N=100000;
const int MAX_M=10000000;
int deep[MAX_N],f[MAX_N],anc[MAX_N][25]; //anc[v][h]表示v结点的h倍祖先的编号
int p[MAX_N],eid;
int n,m;
struct node
{
int to,next;
}e[MAX_N];
void init()
{
memset(anc,0,sizeof(anc));
memset(p,-1,sizeof(p));
eid=0;
}
void insert(int u,int v)
{
e[eid].to=v;
e[eid].next=p[u];
p[u]=eid++;
}
void dfs(int x,int fa) //构建dfs序
{
anc[x][0]=f[x];
for(int i=1;i<=20;i++)
anc[x][i]=anc[anc[x][i-1]][i-1];
for(int i=p[x];i!=-1;i=e[i].next)
if(e[i].to!=fa)
{
f[e[i].to]=x;
deep[e[i].to]=deep[x]+1;
dfs(e[i].to,x);
}
}
int lca(int x,int y) //求x与y的lca
{
if(deep[x]for(int i=20;i>=0;i--)
if(deep[anc[x][i]]>=deep[y])
x=anc[x][i];
if(x==y) return x;
for(int i=20;i>=0;i--)
if(anc[x][i]!=anc[y][i])
x=anc[x][i],y=anc[y][i];
return f[x];
}
int main()
{
init();
cin>>n>>m; //顶点数,边数
for(int i=1;i<=m;i++)
{
int u,v;
cin>>u>>v;
insert(u,v);
insert(v,u);
}
deep[1]=1;
dfs(1,0);
int x,y,q;
cin>>q; //查询次数
for(int i=1;i<=q;i++)
{
cin>>x>>y;
cout<return 0;
}
树上任意两点距离之和:O(n)
#include
#include
#include
using namespace std;
typedef long long ll;
const int maxn = 100005;
const ll mod=1e9+7;
int sum[maxn], n;
ll dp[maxn];
struct Edge
{
int v, w;
};
vector tree[maxn];
void dfs(int cur, int father)
{
sum[cur] = 1;
for(int i = 0; i < tree[cur].size(); i++)
{
int son = tree[cur][i].v;
ll len = tree[cur][i].w;
if(father == son)
continue;
dfs(son, cur);
sum[cur] += sum[son];
sum[cur]%=mod;
dp[cur] =(dp[cur]+dp[son] + (((n-sum[son])*sum[son])%mod * len)%mod)%mod;
}
}
int main()
{
std::ios::sync_with_stdio(false);
calJc();
while(cin>>n)
{
for(int i = 0; i <= n; i++)
tree[i].clear();
memset(sum, 0, sizeof(sum));
memset(dp, 0, sizeof(dp));
for(int i = 0; i < n-1; i++)
{
ll u,v,w;
cin>>u>>v>>w;
Edge t1, t2;
t1.v = v;
t1.w = w;
t2.v = u;
t2.w = w;
tree[u].push_back(t1);
tree[v].push_back(t2);
}
dfs(1,0);
cout<1]<return 0;
}
拓扑排序:
#include
#include
#include
using namespace std;
const int MAX_N=10000;
struct edge
{
int to,next;
}e[MAX_N];
int p[MAX_N],eid;
void init()
{
eid=0;
memset(p,-1,sizeof(p));
}
void insert(int u,int v)
{
e[eid].to=v;
e[eid].next=p[u];
p[u]=eid++;
}
int indegree[MAX_N];
int n;
int topo()
{
queue<int>Q;
for(int i=1;i<=n;i++)
{
if(indegree[i]==0)
Q.push(i);
}
while(!Q.empty())
{
int now=Q.front();
cout<<"visting"<for(int i=p[now];i!=-1;i=e[i].next)
{
int v=e[i].to;
indegree[v]--;
if(indegree[v]==0)
Q.push(v);
}
}
}
欧拉图:
void dfs(int now)
{
int k;
for(k=p[now];k!=-1;k=e[k].next)
{
if(!vst[k])
{
vst[k]=true;
vst[k^1]=true;
dfs(e[k].to);
ans[ansi++]=k;
}
}
}
dfs结束后,ans中存储的就是欧拉图,可通过vst判断图的联通性,每个点都被更新则全联通
强连通分量:
Tarjan算法:O(V+E)
#include
#include
using namespace std;
const int MAX_N=10000;
struct edge
{
int to,next;
}e[MAX_N];
int p[MAX_N],eid;
void init()
{
eid=0;
memset(p,-1,sizeof(p));
}
void insert(int u,int v)
{
e[eid].to=v;
e[eid].next=p[u];
p[u]=eid++;
}
int dfn[MAX_N],low[MAX_N]; //当前时间戳,最早次序号,dfn只能初始化为0!
int idx=0; //时间戳初始化为0
int belong[MAX_N],scc=0; //belong记录每个顶点属于哪个强连通分量,scc为强连通分量总数
int s[MAX_N],top=0; //模拟栈
bool instack[MAX_N]; //是否在栈中
void tarjan(int u)
{
dfn[u]=low[u]=++idx;
s[top++]=u;
instack[u]=true;
for(int i=p[u];i!=-1;i=e[i].next)
{
int v=e[i].to;
if(!dfn[v])
{
tarjan(v);
low[u]=min(low[u],low[v]);
}
else if(instack[v])
{
low[u]=min(low[u],dfn[v]);
}
}
if(dfn[u]==low[u])
{
++scc;
do
{
belong[s[--top]]=scc;
instack[s[top]]=false;
}
while(s[top]!=u);
}
}
int main()
{
init();
int n,m;
cin>>n>>m;
for(int i=0;iint a,b;
cin>>a>>b;
insert(a,b);
}
// tarjan(0);
for(int i=1;i<=n;i++) //用这种方法更新全部点
if(dfn[i]==0)
tarjan(i);
for(int i=1;i<=n;i++)
cout<"->"<return 0;
}
最短路:
1.Bellman-Ford:
可处理负权边,O(V*E)
struct edge{int from,to,cost};
edge es[N]; //边
int d[N],V,S; //最短距离,顶点数,边数
void bellman_ford(int s)
{
memset(d,INF,sizeof(d));
d[s]=0;
while(true)
{
bool update = false;
for(int i=0;iif(d[e.from] != INF && d[e.to]>d[e.from]+e.cost)
{
d[e.to]=d[e.from]+e.cost;
update = true;
}
}
if(!update) break;
}
}
检测负环:
基于Bellman-ford
bool find_negative_loop() //返回true表示存在负圈
{
memset(d,0,sizeof(d));
for(int i=0;ifor(int j=0;jif(d[e.to]>d[e.from]+e.cost)
d[e.to]=d[e.from]+e.cost;
if(i==V-1) //如果第n次仍然更新了,则存在负圈
return truel
}
}
return false;
}
2.朴素Dijkstra:
不能处理负权边,O(E*logV)
#include
#include
#include
using namespace std;
const int MAX_N=1000;
const int MAX_V=1000;
const int INF=0x3f3f3f3f;
struct edge
{
int to,cost;
};
typedef pair<int,int>P; //first是最短距离,second是顶点编号
int V;
vector G[MAX_N];
int d[MAX_V];
void Dijstra(int s)
{
//通过指定greater参数,堆按照first从小到大顺序取出值
priority_queue< P,vector,greater > que;
fill(d,d+V,INF);
d[s]=0;
que.push(P(0,s));
while(!que.empty())
{
P now=que.top();
que.pop();
int v=now.second;
if(d[v]continue;
for(int i=0;iif(d[e.to]>d[v]+e.cost)
{
d[e.to]=d[v]+e.cost;
que.push(P(d[e.to],e.to));
}
}
}
}
Dijkstra—堆优化:
O((V+E)logV) 推荐使用!
const int MAX_N = 10000;
const int MAX_M = 100000;
const int inf = 0x3f3f3f3f;
struct edge {
int v, w, next;
} e[MAX_M];
int p[MAX_N], eid;
int n; //顶点数设为全局变量
void mapinit() {
memset(p, -1, sizeof(p));
eid = 0;
}
void insert(int u, int v, int w) { // 插入带权有向边
e[eid].v = v;
e[eid].w = w;
e[eid].next = p[u];
p[u] = eid++;
}
void insert2(int u, int v, int w) { // 插入带权双向边
insert(u, v, w);
insert(v, u, w);
}
typedef pair<int, int> PII;
set > min_heap; // 用 set 来伪实现一个小根堆,并具有映射二叉堆的功能。堆中 pair 的 second 表示顶点下标,first 表示该顶点的 dist 值
set >:: iterator iter;
int dist[MAX_N]; // 存储单源最短路的结果
bool vst[MAX_N]; // 标记每个顶点是否在集合 U 中
bool dijkstra(int s) {
// 初始化 dist、小根堆和集合 U
memset(vst, 0, sizeof(vst));
memset(dist, 0x3f, sizeof(dist));
min_heap.insert(make_pair(0, s));
dist[s] = 0;
for (int i = 0; i < n; ++i) {
if (min_heap.size() == 0) { // 如果小根堆中没有可用顶点,说明有顶点无法从源点到达,算法结束
return false;
}
// 获取堆顶元素,并将堆顶元素从堆中删除
iter = min_heap.begin();
int v = iter->second;
min_heap.erase(*iter);
vst[v] = true;
// 进行和普通 dijkstra 算法类似的松弛操作
for (int j = p[v]; j != -1; j = e[j].next) {
int x = e[j].v;
if (!vst[x] && dist[v] + e[j].w < dist[x]) {
// 先将对应的 pair 从堆中删除,再将更新后的 pair 插入堆
min_heap.erase(make_pair(dist[x], x));
dist[x] = dist[v] + e[j].w;
min_heap.insert(make_pair(dist[x], x));
}
}
}
return true; // 存储单源最短路的结果
}
路径还原:
基于Dijkstra,使用邻接矩阵
int prev[MAX_V];
int d[MAX_V];
int V;
int cost[MAX_V][MAX_V];
bool used[MAX_V]
void Dijkstra(int s)
{
fill(d,d+v,INF);
fill(used,used+V,false);
fill(prev,prev+V,-1);
d[s]=0;
while(true)
{
int v=-1;
for(int u=0;uif(!used[u]&& (v==-1 || d[u]if(v==-1) break;
used[v]=true;
for(int u=0;uif(d[u]>d[v]+cost[v][u])
d[u]=d[v]+cost[v][u];
prev[u]=v;
}
}
}
vector <int>get_path(int t)
{
vector<int>path;
for(;t!=-1;t=prev[t])
path.push_back(t);
reverse(path.begin(),path.end());
return path;
}
3.floyd:
计算任意两点间最短路,O(v^3)
const int inf = 0x3f3f3f3f;
int g[MAX_N][MAX_N]; // 算法中的 G 矩阵
// 初始化 g 矩阵
void init() {
for (int i = 0; i < n; ++i) {
for (int j = 0; j < n; ++j) {
if (i == j) {
g[i][j] = 0;
} else {
g[i][j] = inf;
}
}
}
}
// 插入一条带权有向边
void insert(int u, int v, int w) {
g[u][v] = w;
}
// 核心代码
void floyd() {
for (int k = 0; k < n; ++k) {
for (int i = 0; i < n; ++i) {
for (int j = 0; j < n; ++j) {
if (g[i][k] + g[k][j] < g[i][j]) {
g[i][j] = g[i][k] + g[k][j];
}
}
}
}
}
4.SPFA:
可处理负权,但不能处理负环,能判断负环,稀疏图O(E),稠密图O(VE)
bool inq[MAX_N];
int cnt[MAX_N]; //记录每个顶点入队次数,若某点入队超过n次,则存在负环
int d[MAX_N]; // 如果到顶点 i 的距离是 0x3f3f3f3f,则说明不存在源点到 i 的最短路
void spfa(int s) {
memset(inq, 0, sizeof(inq));
memset(d, 0x3f, sizeof(d));
d[s] = 0;
inq[s] = true;
queue<int> q;
q.push(s);
while (!q.empty()) {
int u = q.front();
q.pop();
if(cnt[u]>n)
cout<<存在负环;
inq[u] = false;
for (int i = p[u]; i != -1; i = e[i].next) {
int v = e[i].v;
if (d[u] + e[i].w < d[v]) {
d[v] = d[u] + e[i].w;
if (!inq[v]) {
q.push(v);
cnt[v]++;
inq[v] = true;
}
}
}
}
}
K短路:spfa+A*
#include
#include
#include
#include
using namespace std;
int const maxn=10005;
int const maxm=1005;
int const INF=0x3f3f3f3f;
int n,m,S,E,T,k;
int p1[maxn],p2[maxn],eid1,eid2;
bool vis[maxm];
int dis[maxm];
struct node
{
int to,next,cost;
}e1[maxn],e2[maxn];
struct node2
{
int to,g,f;
bool operator<(const node2 &r ) const
{
if(r.f==f)
return r.greturn r.fvoid init()
{
eid1=1;
eid2=1;
memset(p1,-1,sizeof(p1));
memset(p2,-1,sizeof(p2));
}
void insert1(int u,int v,int w) //正向存图
{
e1[eid1].to=v;
e1[eid1].cost=w;
e1[eid1].next=p1[u];
p1[u]=eid1++;
}
void insert2(int u,int v,int w) //反向存图
{
e2[eid2].to=v;
e2[eid2].cost=w;
e2[eid2].next=p2[u];
p2[u]=eid2++;
}
void spfa(int s) //处理出从终点到所有点的最短路
{
queue<int>Q;
memset(vis,0,sizeof(vis));
memset(dis,INF,sizeof(dis));
dis[s]=0;
vis[s]=1;
Q.push(s);
while(!Q.empty())
{
int u=Q.front();
vis[u]=0;
Q.pop();
for(int i=p2[u];i!=-1;i=e2[i].next)
{
int v=e2[i].to;
if(dis[v]>dis[u]+e2[i].cost)
{
dis[v]=dis[u]+e2[i].cost;
if(!vis[v])
{
vis[v]=1;
Q.push(v);
}
}
}
}
}
int Astar() //A*求k短路
{
node2 e,now;
int cnt=0;
priority_queueQ;
if(S==E)
k++;
if(dis[S]==INF)
return -1;
e.to=S;
e.g=0;
e.f=e.g+dis[e.to];
Q.push(e);
while(!Q.empty())
{
e=Q.top();
Q.pop();
if(e.to==E)
cnt++;
if(cnt==k)
return e.g;
int u=e.to;
for(int i=p1[u];i!=-1;i=e1[i].next)
{
now.to=e1[i].to;
now.g=e.g+e1[i].cost;
now.f=now.g+dis[now.to];
Q.push(now);
if(now.g>T) return -1;
}
}
return -1;
}
int main()
{
//std::ios::sync_with_stdio(false);
while(~scanf("%d%d",&n,&m))
{
init();
scanf("%d%d%d%d",&S,&E,&k,&T); //起点,终点,k短路,限定条件
for(int i=1;i<=m;i++)
{
int u,v,w;
scanf("%d%d%d",&u,&v,&w);
insert1(u,v,w);
insert2(v,u,w);
}
spfa(E);
Astar();
int ans=Astar();
printf("%d",ans); //输出k短路长度
}
}
差分约束系统:
即利用最短路算法解不等式,形如a-b<=k,则建立有向边a->b,权值为k
若a=b,即相当a<=b && a>=b ,即建立a->b 和 b->a 两条权值为0的边
加入超级源点,到达所有顶点,且权值为0
利用spfa算法,判断负环,若不存在,则该系统有解,若存在,则该系统无解
最小生成树:
1.Prim:O(n^2)
int cost[MAX_N][MAX_N]; //cost[u][v] 表示边e=(u,v)的权值,不存在为INF
int mincost[MAX_N]; //从集合T出发的边到每个顶点的最小权值
bool used[MAX_N]; //顶点i是否包含在集合T中
int n; //顶点数
int prim()
{
for(int i=0;i//初始化
{
mincost[i]=INF;
used[i]=false;
}
mincost[0]=0;
int res=0;
while(true)
{
int v=-1; //从不属于T的顶点中选取T到其权值最小的顶点
for(int u=0;uif(!used[u] && (v==-1 || mincost[u]if(v==-1) break; //没有可达点
used[v]=true; //把顶点v加入x
res += mincost[v]; //更新结果
for(int u=0;umin(mincost[u],cost[v][u]);
}
return res; //返回最小生成树总权值
}
2.Kruskal:O(eloge)
#include
#include
#include
#include
using namespace std;
const int MAX_N = 100000; // 最大顶点数
const int MAX_M = 100000; // 最大边数
struct edge {
int u, v, w;
}e[MAX_M];
int fa[MAX_N], n, m; // fa 数组记录了并查集中结点的父亲
bool cmp(edge a,edge b) {
return a.w < b.w;
}
// 并查集相关代码
int ancestor(int x) { // 在并查集森林中找到 x 的祖先,也是所在连通块的标识
if(fa[x] == x) return fa[x];
else return fa[x] = ancestor(fa[x]);
}
int same(int x, int y) { // 判断两个点是否在一个连通块(集合)内
return ancestor(x) == ancestor(y);
}
void merge(int x, int y) { // 合并两个连通块(集合)
int fax = ancestor(x), fay = ancestor(y);
fa[fax] = fay;
}
int Kruskal()
{
for (int i = 1; i <= n; i++) {
fa[i] = i;
}
int rst = n, ans = 0; // rst 表示还剩多少个集合,ans 保存最小生成树上的总边权
for (int i = 1; i <= m && rst > 1; i++) {
int x = e[i].u, y = e[i].v;
if (same(x, y)) {
continue; // same 函数是查询两个点是否在同一集合中
} else {
merge(x, y); // merge 函数用来将两个点合并到同一集合中
rst--; // 每次将两个不同集合中的点合并,都将使 rst 值减 1
ans += e[i].w; // 这条边是最小生成树中的边,将答案加上边权
}
}
return ans;
}
int main() {
scanf("%d%d", &n, &m); // n 为点数,m 为边数
for (int i = 1; i <= m; i++) {
scanf("%d%d%d", &e[i].u, &e[i].v, &e[i].w); // 用边集数组存放边,方便排序和调用
}
sort(e + 1, e + m + 1, cmp); // 对边按边权进行升序排序
cout<return 0;
}
二分图:
二分图判断:
bool check()
{
memset(used,-1,sizeof(used));
queue<int>Q;
Q.push(1);
used[1]=0;
while(!Q.empty())
{
int now=Q.front();
for(int i=1;i<=n;i++) //遍历所有点
{
if(map[now][i]==0) //邻接矩阵存图
continue;
int v=i;
if(used[v]==-1)
{
used[v]=(used[now]+1)%2;
Q.push(v);
}
else
{
if(used[v]==used[now])
return false;
}
}
Q.pop();
}
return true;
}
匈牙利算法:
const int MAX_N = 100; // X 集合中的顶点数上限
const int MAX_M = 10000; // 总的边数上限
struct edge {
int v, next;
} e[MAX_M];
int p[MAX_N], eid;
void init() {
memset(p, -1, sizeof(p));
eid = 0;
}
void insert(int u, int v) { // 从 X 集合顶点 u 到 Y 集合顶点 v 连一条边,注意 u 和 v 的编号无关
e[eid].v = v;
e[eid].next = p[u];
p[u] = eid++;
}
bool vst[MAX_N]; // 标记一次 dfs 过程中,Y 集合中的顶点是否已访问
int ans[MAX_N]; // 标记 Y 集合中的顶点匹配的 X 集合中的顶点编号
int n, m; // n 表示 X 集合中的顶点数,假设顶点编号为 0..n-1
bool dfs(int u) {
for (int i = p[u]; i != -1; i = e[i].next) {
int v = e[i].v;
if (!vst[v]) { // 如果 Y 集合中的 v 还没有被访问
vst[v] = true;
if (ans[v] == -1 || dfs(ans[v])) { // 如果 v 没有匹配点,或 v 的匹配点能找到一条到一个未匹配点的增广路,则将 v 的匹配点设为 u
ans[v] = u;
return true;
}
}
}
return false; // 没找到增广路
}
int maxmatch() {
int cnt = 0;
memset(ans, -1, sizeof(ans)); // 初始将所有 Y 集合中顶点的匹配编号设为 -1
for (int i = 0; i < n; ++i) {
memset(vst, 0, sizeof(vst)); // 进行 dfs 前,将 vst 清空
cnt += dfs(i); // 如果找到增广路,则将 cnt 累加 1
}
return cnt; // cnt 是找到增广路的次数,也是总的最大匹配数
}
网络流
1.最大流—Dinic算法:
#include
#include
#include
using namespace std;
const int MAX_N=1000;
const int MAX_M=100000;
const int INF=0x3f3f3f3f;
struct edge
{
int v,c,next;
}e[MAX_M];
int p[MAX_N],eid;
void init()
{
memset(p,-1,sizeof(p));
eid=0;
}
void insert(int u,int v,int c)
{
e[eid].v=v;
e[eid].next=p[u];
e[eid].c=c;
p[u]=eid++;
}
void addedge(int u,int v,int c)
{
insert(u,v,c);
insert(v,u,0);
}
int S,T; //源点和汇点
int d[MAX_N]; //d表示当前点的层数(level)
bool bfs()
{
memset(d,-1,sizeof(d));
queue<int>q;
q.push(S);
d[S]=0;
while(!q.empty())
{
int u=q.front();
q.pop();
for(int i=p[u];i!=-1;i=e[i].next)
{
int v=e[i].v;
if(e[i].c>0 && d[v]==-1)
{
q.push(v);
d[v]=d[u]+1;
}
}
}
return (d[T]!=-1);
}
int dfs(int u,int f)
{
if(u==T)
return f;
int res=0;
for(int i=p[u];i!=-1;i=e[i].next)
{
int v=e[i].v;
if(e[i].c>0 && d[u]+1 == d[v])
{
int tmp=dfs(v,min(f,e[i].c)); //递归计算顶点v,用c(u,v)更新当前流量上限
f-=tmp;
e[i].c-=tmp;
res+=tmp;
e[i^1].c+=tmp; //修改反向弧流量
if(f==0) //流量达到上限,不必继续搜索
break;
}
}
if(res==0) //当前没有经过顶点u的流,不必再搜索顶点u
d[u]=-1;
return res;
}
int maxf() //计算最大流
{
int res=0;
while(bfs())
{
res+=dfs(S,INF); //初始流量上限为INF
}
return res;
}
int main()
{
int t,cnt=1;
cin>>t;
while(t--)
{
init();
int m,n;
cin>>n>>m;
for(int i=0;iint a,b,c;
cin>>a>>b>>c;
addedge(a,b,c);
}
/* for(int k=1;k<=n;k++) //输出图,用于检测图的读入是否正确
for(int i=p[k];i!=-1;i=e[i].next)
{
cout<"<
S=1;
T=n;
cout<<"Case "<< cnt++ <<": ";
cout<return 0;
}
2.最小费用流–spfa费用流:
#include
#include
#include
using namespace std;
const int MAX_N = 10005;
const int MAX_M = 100005;
const int inf = 0x3f3f3f3f;
struct edge
{
int v, c, w, next; // v 表示边的另一个顶点,c 表示当前剩余容量,w 表示单位流量费用
} e[MAX_M];
int p[MAX_N], s, t, eid; // s 表示源点,t 表示汇点,需要在进行 costflow 之前设置完毕
void init()
{
memset(p, -1, sizeof(p));
eid = 0;
}
void insert(int u, int v, int c, int w) {
e[eid].v = v;
e[eid].c = c;
e[eid].w = w;
e[eid].next = p[u];
p[u] = eid++;
}
void addedge(int u, int v, int c, int w) {
insert(u, v, c, w);
insert(v, u, 0, -w);
}
bool inq[MAX_N];
int d[MAX_N]; // 如果到顶点 i 的距离是 0x3f3f3f3f,则说明不存在源点到 i 的最短路
int pre[MAX_N]; // 最短路中连向当前顶点的边的编号
bool spfa()
{ // 以源点 s 为起点计算单源最短路,如果不存在从 s 到 t 的路径则返回 false,否则返回 true
memset(inq, 0, sizeof(inq));
memset(d, 0x3f, sizeof(d));
memset(pre, -1, sizeof(pre));
d[s] = 0;
inq[s] = true;
queue<int> q;
q.push(s);
while (!q.empty()) {
int u = q.front();
q.pop();
inq[u] = false;
for (int i = p[u]; i != -1; i = e[i].next) {
if (e[i].c) {
int v = e[i].v;
if (d[u] + e[i].w < d[v]) {
d[v] = d[u] + e[i].w;
pre[v] = i;
if (!inq[v]) {
q.push(v);
inq[v] = true;
}
}
}
}
}
return pre[t] != -1;
}
int costflow() { // 计算最小费用最大流
int ret = 0; // 累加和
while(spfa()) {
int flow = inf;
for(int i = t; i != s; i = e[pre[i]^1].v) {
flow = min(e[pre[i]].c, flow); // 计算当前增广路上的最小流量
}
for(int i = t; i != s; i = e[pre[i]^1].v) {
e[pre[i]].c -= flow;
e[pre[i]^1].c += flow;
ret += e[pre[i]].w * flow;
}
}
return ret;
}
int main() //以最短来回路为例
{
int n,m;
cin>>n>>m;
init();
s=0;
t=n+1;
addedge(s,1,2,0);
for(int i=0;iint a,b,c;
cin>>a>>b>>c;
addedge(a,b,1,c);
addedge(b,a,1,c);
}
addedge(n,t,2,0);
int ans=costflow();
cout<return 0;
}