网络流专题
网络流基础知识
网络
网络是指一个有向图
网络中有两个特殊的点:源点
流
设
- 容量限制:对于每条边,流经该边的流量不得超过该边的容量,即
- 斜对称性:每条边的流量与其相反边的流量之和为 0,即
- 流守恒性:从源点流出的流量等于汇点流入的流量,即
下面给出完整的定义:
那么
一般而言也可以把网络流理解为整个图的流量,而这个流量必满足上述三个性质。
增广路
如果一条从源点到汇点的简单路径、路径上所有边的权值都大于零,那么这条路径被称为增广路。
残量网络
假设当前有一网络
最大流(dinic 算法)
简述
所有从源点到汇点的路径最终到汇点时候的流量和。
思路
在每次增广之前先对残量网络做
当前弧优化
在每次
如果当前到
如果当前到 cur
记录每次合法的点。
例题
代码
#include <bits/stdc++.h>
#define ll long long
#define INF 0x3f3f3f3f3f
using namespace std;
char c;
ll rd(){
ll x = 0, f = 1; c = getchar();
while(! isdigit(c)){if(c == '-')f = - f; c = getchar();}
while(isdigit(c)){x = (x << 3) + (x << 1) + (c ^ 48); c = getchar();}
return x * f;
}
const int N = 3e3 + 5;
int n, m, dep[N], hd[N], cur[N], cnt = 1, s = N - 2, t = N - 1;
ll sum;
struct edge{
int nxt, to; ll w;
}e[3000005];
void add(int u, int v, ll w){
e[++cnt] = (edge){hd[u], v, w}; hd[u] = cnt;
}
int bfs(){
memset(dep, 0, sizeof dep);
queue < int > q; q.push(s); dep[s] = 1; cur[s] = hd[s];
while(! q.empty()){
int u = q.front(); q.pop();
for(int i = hd[u]; i; i = e[i].nxt){
int v = e[i].to;
if(! dep[v] and e[i].w){
cur[v] = hd[v];
dep[v] = dep[u] + 1;
q.push(v);
}
}
}
return dep[t];
}
ll dfs(int u, ll lim){
if(u == t or ! lim)return lim;
ll k, flow = 0;
for(int &i = cur[u]; i and lim; i = e[i].nxt){
int v = e[i].to; ll f = e[i].w;
if((dep[v] == dep[u] + 1) and (k = dfs(v, min(lim, f)))){
e[i].w -= k; e[i ^ 1].w += k;
flow += k; lim -= k;
if(! lim)break;
}
}
if(! flow)dep[u] = N;
return flow;
}
ll dinic(){
ll maxflow = 0, k;
while(bfs()){
while(k = dfs(s, INF))maxflow += k;
}
return maxflow;
}
signed main(){
n = rd(); m = rd(); s = rd(); t = rd();
for(int i = 1; i <= m; ++i){
int u = rd(), v = rd(); ll w = rd();
add(u, v, w); add(v, u, 0);
}
cout << dinic();
return 0;
}
最大流之预流推进
算法
此算法在求解过程中忽略流守恒性,并每次对一个结点更新信息,以求解最大流。
通用的预流推进算法
预流推进通过每次单点更新的方法求解最大流。
在算法中,我们维护的流函数
如果
预流推进最重要的是维护每个结点的高度
高度函数
预流推进维护以下的一个映射
其中
推进(Push)
当一个结点
修改高度(Relabel)
修改高度又叫重贴标签,如果
每次重贴标签将
过程
还是每次进行
算法
相较于
优化
个人觉得其实这并不算严格意义来讲的优化,
优化
在每次重贴标签时,我们直接让结点的高度变成
我们可以使用 b
,b[i]
记录所有高度为
例题
代码
#include <bits/stdc++.h>
#define ll long long
using namespace std;
ll rd(){
ll x = 0, f = 1; char c = getchar();
while(! isdigit(c)){if(c == '-')f = - f; c = getchar();}
while(isdigit(c)){x = (x << 3) + (x << 1) + (c ^ 48); c = getchar();}
return x * f;
}
const int N = 1205, M = 1.2e5 + 5, INF = 0x3f3f3f3f;
int n, m, s, t, hd[N], cnt = 1;
struct edge{
int nxt, to; ll w;
}e[M << 1];
void add(int u, int v, ll w){
e[++cnt] = (edge){hd[u], v, w}; hd[u] = cnt;
}
int h[N], gap[N], height;
ll exc[N];
stack < int > b[N];
bool pushin(int u){
bool op = u == s;
for(int i = hd[u]; i; i = e[i].nxt){
int v = e[i].to; ll w = e[i].w;
if(! w or ! op and h[u] != h[v] + 1 or h[u] == INF)continue;
ll k = op ? w : min(w, exc[u]);
if(v != s and v != t and ! exc[v])b[h[v]].push(v), height = max(height, h[v]);
exc[u] -= k; exc[v] += k; e[i].w -= k; e[i ^ 1].w += k;
if(! exc[u])return 0;
}
return 1;
}
void relabel(int u){
h[u] = INF;
for(int i = hd[u]; i; i = e[i].nxt)if(e[i].w)h[u] = min(h[u], h[e[i].to]);
if(++h[u] < n){
b[h[u]].push(u);
height = max(height, h[u]);
++gap[h[u]];
}
}
bool bfs(){
memset(h, 0x3f, sizeof h);
queue < int > q;
q.push(t); h[t] = 0;
while(! q.empty()){
int u = q.front(); q.pop();
for(int i = hd[u]; i; i = e[i].nxt){
int v = e[i].to;
if(e[i ^ 1].w and h[v] > h[u] + 1)h[v] = h[u] + 1, q.push(v);
}
}
return h[s] != INF;
}
int sel(){
while(b[height].empty() and height > - 1)--height;
return ~ height ? b[height].top() : 0;
}
ll HLPP(){
if(! bfs)return 0;
memset(gap, 0, sizeof gap);
for(int i = 1; i <= n; ++i)if(h[i] != INF)++gap[h[i]];
h[s] = n; pushin(s);
int u;
while(u = sel()){
b[height].pop();
if(pushin(u)){
if(! --gap[h[u]])
for(int i = 1; i <= n; ++i)if(i != s and i != t and h[i] > h[u] and h[i] < n + 1)h[i] = n + 1;
relabel(u);
}
}
return exc[t];
}
signed main(){
n = rd(); m = rd(); s = rd(); t = rd();
for(int i = 1; i <= m; ++i){
int u = rd(), v = rd(), w = rd();
add(u, v, w); add(v, u, 0);
}
printf("%lld", HLPP());
return 0;
}
最大流最小割定理
割
在一个网络中,一个
割的容量
割
结点集的净流量
我们可以将净流量的概念推广到由结点构成的集合
引理
对于网络中的一个可行流
,其流的值 等于 割中,结点集合 的净流量 。
证明:
考虑根据
- 基本情况:当
只有一个结点的时候, ,根据流的定义有 。 - 归纳步骤:假设当
中有 个点时,有 成立。接下来我们需要证明,将一个点从 变到 后,净流量不变。
假设现在将点
- 从
流到 的流量总和; - 从
流到 的流量总和; - 从
流到 的流量总和; - 从
流到 的流量总和;
设以上四种情况的流量总和分别为
在
根据
所以当
最大流最小割定理
最大流最小割定理:在任意网络中,设
为最大流, 是一个最小割,则 。
证明:
证明
有了之前证的引理,可得:
证明
即
我们可以用
假设有
证毕。
最大流的一些题
P3254 圆桌问题
题目描述
有来自
会议的餐厅共有
为了使代表们充分交流,希望从同一个单位来的代表不在同一个餐桌就餐。请给出一个满足要求的代表就餐方案。
题解
源点向每个单位连边,容量为单位人数;每个单位向每张餐桌连边,容量为
代码
#include <bits/stdc++.h>
#define ll long long
#define INF 0x3f3f3f3f3f
using namespace std;
ll rd(){
ll x = 0, f = 1; char c = getchar();
while(! isdigit(c)){if(c == '-')f = - f; c = getchar();}
while(isdigit(c)){x = (x << 3) + (x << 1) + (c ^ 48); c = getchar();}
return x * f;
}
const int N = 1e5 + 5;
int n, m, dep[N], hd[N], cur[N], cnt = 1, s = 1, t = N - 1;
ll sum;
struct edge{
int nxt, to; ll w;
}e[N << 1];
void add(int u, int v, int w){
e[++cnt] = (edge){hd[u], v, w}; hd[u] = cnt;
}
int bfs(){
memset(dep, 0, sizeof dep);
queue < int > q; q.push(s); dep[s] = 1;
while(! q.empty()){
int u = q.front(); q.pop();
for(int i = hd[u]; i; i = e[i].nxt){
int v = e[i].to;
if(! dep[v] and e[i].w){
dep[v] = dep[u] + 1;
q.push(v);
}
}
}
return dep[t];
}
ll dfs(int u, ll lim){
if(u == t or ! lim)return lim;
ll k, flow = 0;
for(int &i = cur[u]; i; i = e[i].nxt){
int v = e[i].to; ll f = e[i].w;
if((dep[v] == dep[u] + 1) and (k = dfs(v, min(lim, f)))){
e[i].w -= k; e[i ^ 1].w += k;
flow += k; lim -= k;
}
}
if(! flow)dep[u] = N;
return flow;
}
ll dinic(){
ll maxflow = 0, k;
while(bfs()){
memcpy(cur, hd, sizeof hd);
while(k = dfs(s, INF))maxflow += k;
}
return maxflow;
}
signed main(){
n = rd(); m = rd();
for(int i = 1; i <= n; ++i){
ll u = rd(); sum += u;
add(s, i + 1, u); add(i + 1, s, 0);
for(int j = 1; j <= m; ++j)add(i + 1, j + n + 1, 1), add(j + n + 1, i + 1, 0);
}
//cout << sum << '\n';
for(int i = 1; i <= m; ++i){
ll u = rd();
add(i + n + 1, t, u); add(t, i + n + 1, 0);
}
int ans = dinic(); //cout << ans << '\n';
if(ans != sum)return puts("0"), 0;
puts("1");
for(int u = 1; u <= n; ++u){
for(int i = hd[u + 1]; i; i = e[i].nxt){
int v = e[i].to;
if(v == s)continue;
if(! e[i].w)printf("%d ", v - n - 1);
}
puts("");
}
return 0;
}
P2766 最长不下降子序列问题
题目描述
给定正整数序列
- 计算其最长不下降子序列的长度
。 - 如果每个元素只允许使用一次,计算从给定的序列中最多可取出多少个长度为
的不下降子序列。 - 如果允许在取出的序列中多次使用
和 (其他元素仍然只允许使用一次),则从给定序列中最多可取出多少个不同的长度为 的不下降子序列。
令
题解
第一问
第二问因为每个点经过多次,考虑拆点做。对于第一问每个 i>j&&dp[i]==dp[j]+1&&a[i]>=a[j]
连一条容量为一的边,如果 dp[i]==1
就从源点向 dp[i]==ans1
就从
第三问是第二问的变式,即可以多次使用第一个点和最后一个点。判一下最后一个点的 dp[i]==ans1
,然后源点到 INF
的边。继续在残量网络上跑
代码
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define Fl(i, l, r) for(int i = l; i <= r; i++)
#define Fr(i, r, l) for(int i = r; i >= l; i--)
const int N = 505, M = 2e6 + 5, INF = 1e9;
int n, a[N], ans1, ans2, ans3, dp[N], s, t = 5e3;
int hd[M], nxt[M], to[M], w[M], now[M], cnt;
void add(int u, int v, int dis){
nxt[++cnt] = hd[u]; hd[u] = cnt; to[cnt] = v; now[cnt] = u; w[cnt] = dis;
nxt[++cnt] = hd[v]; hd[v] = cnt; to[cnt] = u; now[cnt] = v; w[cnt] = dis;
}
int f[100 * N];
bool bfs(int s, int t){
memset(f, 0, sizeof f);
queue < int > q; q.push(s); f[s] = 1;
while(! q.empty()){
int u = q.front(); q.pop();
if(u == t)return true;
for(int i = hd[u]; i; i = nxt[i]){
int v = to[i], d = w[i];
if(f[v] == 0 and d)q.push(v), f[v] = f[u] + 1;
}
}
return false;
}
int dfs(int u, int maxflow, int t){
if(u == t)return maxflow;
int ret = 0;
for(int i = hd[u]; i and ret < maxflow; i = nxt[i]){
int v = to[i], d = w[i];
if(f[v] == f[u] + 1 and d){
int minflow = min(maxflow - ret, d);
d = dfs(v, minflow, t);
w[i] -= d; w[i ^ 1] += d; ret += d;
}
}
if(! ret)f[u] = M;
return ret;
}
void solve(){
cin >> n;
if(n == 1){cout << 1 << '\n' << 1 << '\n' << 1; return;}
Fl(i, 1, n)cin >> a[i]; dp[1] = 1;
Fl(i, 2, n){
int k = 0;
Fl(j, 1, i - 1)if(a[j] <= a[i] and dp[k] < dp[j])k = j;
dp[i] = dp[k] + 1;
}
Fl(i, 1, n)ans1 = max(ans1, dp[i]);
Fl(i, 1, n){
if(dp[i] == 1)add(s, i, 1);
if(dp[i] == ans1)add(i + n, t, 1);
add(i, i + n, 1);
}
Fl(i, 1, n)Fl(j, 1, i - 1)if(a[i] >= a[j] and dp[j] + 1 == dp[i])add(j + n, i, 1);
while(bfs(s, t))ans2 += dfs(s, INF, t); ans3 = ans2;
add(s, 1, INF); add(1, 1 + n, INF); if(dp[n] == ans1)add(n, 2 * n, INF), add(2 * n, t, INF);
while(bfs(s, t))ans3 += dfs(s, INF, t);
cout << ans1 << '\n' << ans2 << '\n' << ans3;
}
signed main(){
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
solve(); return 0;
}
[AGC038F] Two Permutations
题目描述
给定两个
要求构造两个
且必须满足条件:
要么等于 ,要么等于 。 要么等于 ,要么等于 。
你需要最大化
题解
对于每个排列,我们可以把它分解成若干个置换环。对于每个置换环
对于给定的两个排列,一共有五种不同情况。
,直接答案减一; ,只有 才会对答案产生影响,如果选 ,整个答案就会减一,否则不变; ,与上一种类似,不再赘述; ,如果两个同选 或自己则答案减一; ,如果两个同选下标 则答案减一。
对于这五种情况,每个置换环有两种选择:选择自己的下标或者自己的值,可以想到最小割。
然后我们将上面五种情况的结果改一下:
,直接答案减一; ,如果 选 , 所在环向汇点连容量为一的边; ,与上一种类似,不再赘述; , 与 所在环互相连容量为一的边; , 所在环向 所在环连容量为一的单向边。
最后直接跑最大流,然后再用总的个数减去跑出来的结果即可。
代码
#include <bits/stdc++.h>
#define ll long long
#define INF 0x3f3f3f3f
using namespace std;
ll rd(){
ll x = 0, f = 1; char c = getchar();
while(! isdigit(c)){if(c == '-')f = - f; c = getchar();}
while(isdigit(c)){x = (x << 3) + (x << 1) + (c ^ 48); c = getchar();}
return x * f;
}
const int N = 2e5 + 5;
int n, s, t, hd[N], cnt = 1, cur[N], ans;
int a[N], b[N], ra[N], rb[N], d[N], top;
struct edge{
int nxt, to, w;
}e[N << 1];
void add(int u, int v, int w){
e[++cnt] = (edge){hd[u], v, w}; hd[u] = cnt;
}
int bfs(){
memset(d, 0, sizeof d);
queue < int > q;
q.push(s); d[s] = 1; cur[s] = hd[s];
while(! q.empty()){
int u = q.front(); q.pop();
for(int i = hd[u]; i; i = e[i].nxt){
int v = e[i].to, w = e[i].w;
if( ! d[v] and w){
d[v] = d[u] + 1;
cur[v] = hd[v];
q.push(v);
}
}
}
return d[t];
}
int dfs(int u, int lim){
if(u == t or ! lim)return lim;
int k, flow = 0;
for(int &i = cur[u]; i and lim; i = e[i].nxt){
int v = e[i].to, f = e[i].w;
if(d[v] == d[u] + 1 and (k = dfs(v, min(lim, f)))){
e[i].w -= k; e[i ^ 1].w += k;flow += k; lim -= k;
}
}
if(! flow)d[u] = t + 1;
return flow;
}
int dinic(){
int res = 0, k;
while(bfs()){
while(k = dfs(s, INF))res += k;
}
return res;
}
signed main(){
n = rd();
for(int i = 1; i <= n; ++i)a[i] = rd() + 1;
for(int i = 1; i <= n; ++i)b[i] = rd() + 1;
for(int i = 1; i <= n; ++i)if(! ra[i]){
ra[i] = a[i] != i ? ++top : top;
int x = a[i];
while(x != i)ra[x] = top, x = a[x];
}
for(int i = 1; i <= n; ++i)if(! rb[i]){
rb[i] = b[i] != i ? ++top : top;
int x = b[i];
while(x != i)rb[x] = top, x = b[x];
}
s = top + 1, t = s + 1;
for(int i = 1; i <= n; ++i)
if(a[i] == i and b[i] == i)++ans;
else if(a[i] != i and b[i] != i){
if(a[i] == b[i]){
add(ra[i], rb[i], 1); add(rb[i], ra[i], 1);
add(ra[i], rb[i], 0); add(rb[i], ra[i], 0);
}
else{
add(rb[i], ra[i], 1); add(ra[i], rb[i], 0);
}
}
else{
if(a[i] == i)add(rb[i], t, 1), add(t, rb[i], 0);
else add(ra[i], s, 0), add(s, ra[i], 1);
}
printf("%d", n - ans - dinic());
return 0;
}
费用流
概念
网络中,每个边除了流量,现在还有一个单位费用
算法
其实就是一个裸贪心,每次找单位费用最小的增广路进行增广,直到图上不存在增广路为止。如果有负环要消环。
将
时间复杂度
例题
代码
#include <bits/stdc++.h>
#define ll long long
using namespace std;
ll rd(){
ll x = 0, f = 1; char c = getchar();
while(! isdigit(c)){if(c == '-')f = - f; c = getchar();}
while(isdigit(c)){x = (x << 3) + (x << 1) + (c ^ 48); c = getchar();}
return x * f;
}
const int N = 5005, M = 5e4 + 5;
int n, m, s, t;
int pren[N], pree[N], val[N], fl[N];
int hd[N], cnt = 1;
bool vis[N];
struct edge{
int nxt, to, d, w;
}e[M << 1];
void add(int u, int v, int d, int w){
e[++cnt] = (edge){hd[u], v, d, w}; hd[u] = cnt;
}
bool SPFA(){
memset(val, 0x3f, sizeof val);
memset(fl, 0x3f, sizeof fl);
memset(vis, 0, sizeof vis);
queue < int > q;
pren[t] = - 1; vis[s] = 1; q.push(s); val[s] = 0;
while(! q.empty()){
int u = q.front(); q.pop();
vis[u] = 0;
for(int i = hd[u]; i; i = e[i].nxt){
int v = e[i].to, d = e[i].d, w = e[i].w;
if(d and val[v] > val[u] + w){
val[v] = val[u] + w;
if(! vis[v]){
vis[v] = 1;
q.push(v);
}
fl[v] = min(fl[u], d);
pren[v] = u; pree[v] = i;
}
}
}
return pren[t] != - 1;
}
int ansfl, ansv;
void MCMF(){
while(SPFA()){
ansfl += fl[t];
ansv += fl[t] * val[t];
int x = t;
while(x != s){
e[pree[x]].d -= fl[t];
e[pree[x] ^ 1].d += fl[t];
x = pren[x];
}
}
}
signed main(){
n = rd(); m = rd(); s = rd(); t = rd();
for(int i = 1; i <= m; ++i){
int u = rd(), v = rd(), fl = rd(), w = rd();
add(u, v, fl, w); add(v, u, 0, - w);
}
MCMF();
printf("%d %d", ansfl, ansv);
return 0;
}
例题
P3358 最长k可重区间集问题
题目描述
给定实直线
这样的集合
对于给定的开区间集合
题解
可以把直线上的每个数看成一个点,每个点向后连一条容量为
这样直接跑费用流会寄,因为有很多无用的点,所以离散化一下,再处理区间端点即可。
代码
#include <bits/stdc++.h>
#define ll long long
using namespace std;
ll rd(){
ll x = 0, f = 1; char c = getchar();
while(! isdigit(c)){if(c == '-')f = - f; c = getchar();}
while(isdigit(c)){x = (x << 3) + (x << 1) + (c ^ 48); c = getchar();}
return x * f;
}
const int N = 1005, M = 1e5 + 5;
int n, k, s, t;
int pren[N], pree[N], val[N], fl[N];
int hd[N], cnt = 1;
bool vis[N];
struct edge{
int nxt, to, d, w;
}e[M];
void add(int u, int v, int d, int w){
e[++cnt] = (edge){hd[u], v, d, w}; hd[u] = cnt;
}
bool SPFA(){
memset(val, 0x3f, sizeof val);
memset(fl, 0x3f, sizeof fl);
memset(vis, 0, sizeof vis);
queue < int > q;
pren[t] = - 1; vis[s] = 1; q.push(s); val[s] = 0;
while(! q.empty()){
int u = q.front(); q.pop();
vis[u] = 0;
for(int i = hd[u]; i; i = e[i].nxt){
int v = e[i].to, d = e[i].d, w = e[i].w;
if(d and val[v] > val[u] + w){
val[v] = val[u] + w;
if(! vis[v]){
vis[v] = 1;
q.push(v);
}
fl[v] = min(fl[u], d);
pren[v] = u; pree[v] = i;
}
}
}
return pren[t] != - 1;
}
int ansv;
void MCMF(){
while(SPFA()){
ansv += fl[t] * val[t];
int x = t;
while(x != s){
e[pree[x]].d -= fl[t];
e[pree[x] ^ 1].d += fl[t];
x = pren[x];
}
}
}
int l[N], r[N], a[N], top;
signed main(){
n = rd(); k = rd();
for(int i = 1; i <= n; ++i){
a[++top] = l[i] = rd();
a[++top] = r[i] = rd();
}
sort(a + 1, a + 1 + top);
top = unique(a + 1, a + 1 + top) - a - 1;
s = top + 1; t = s + 1;
for(int i = 1; i <= n; ++i){
int t = r[i] - l[i];
l[i] = lower_bound(a + 1, a + 1 + top, l[i]) - a;
r[i] = lower_bound(a + 1, a + 1 + top, r[i]) - a;
add(l[i], r[i], 1, - t); add(r[i], l[i], 0, t);
}
add(s, 1, k, 0); add(1, s, 0, 0);
add(top, t, k, 0); add(t, top, 0, 0);
for(int i = 1; i < top; ++i)add(i, i + 1, k, 0), add(i + 1, i, 0, 0);
MCMF();
printf("%d", - ansv);
return 0;
}
P2469 [SDOI2010] 星际竞速
题目描述
10 年一度的银河系赛车大赛又要开始了。作为全银河最盛大的活动之一,夺得这个项目的冠军无疑是很多人的梦想,来自杰森座
赛车大赛的赛场由
由于赛制非常开放,很多人驾驶着千奇百怪的自制赛车来参赛。这次悠悠驾驶的赛车名为超能电驴,这是一部凝聚了全银河最尖端科技结晶的梦幻赛车。作为最高科技的产物,超能电驴有两种移动模式:高速航行模式和能力爆发模式。在高速航行模式下,超能电驴会展开反物质引擎,以数倍于光速的速度沿星际航路高速航行。在能力爆发模式下,超能电驴脱离时空的束缚,使用超能力进行空间跳跃——在经过一段时间的定位之后,它能瞬间移动到任意一个行星。
天不遂人愿,在比赛的前一天,超能电驴在一场离子风暴中不幸受损,机能出现了一些障碍:在使用高速航行模式的时候,只能由每个星球飞往引力比它大的星球,否则赛车就会发生爆炸。
尽管心爱的赛车出了问题,但是悠悠仍然坚信自己可以取得胜利。他找到了全银河最聪明的贤者——你,请你为他安排一条比赛的方案,使得他能够用最少的时间完成比赛。
题解
对于“高速航行模式”和“能力爆发模式”分别建点,因为每个点只用去一次,所以所有边容量为一。对于“能力爆发模式”,由于任意点都可以到当前点,所以直接从源点向所有“能力爆发模式”点连边,费用为时间;对于“高速航行模式”,从源点向所有“高速航行模式”点连边,费用为零,再从编号小的点向大的“能力爆发模式”的点正常连边,费用为时间。最后从所有“能力爆发模式”点向汇点连边,费用为零即可。
代码
#include <bits/stdc++.h>
#define ll long long
#define INF 0x3f3f3f3f
using namespace std;
ll rd(){
ll x = 0, f = 1; char c = getchar();
while(! isdigit(c)){if(c == '-')f = - f; c = getchar();}
while(isdigit(c)){x = (x << 3) + (x << 1) + (c ^ 48); c = getchar();}
return x * f;
}
const int N = 1e4 + 5, M = 1e5 + 5;
int n, m, s, t, top, a[N];
int pren[N], pree[N], val[N], fl[N];
int hd[N], cnt = 1;
bool vis[N];
struct edge{
int nxt, to, d, w;
}e[M];
void add(int u, int v, int d, int w){
e[++cnt] = (edge){hd[u], v, d, w}; hd[u] = cnt;
}
bool SPFA(){
memset(val, 0x3f, sizeof val);
memset(fl, 0x3f, sizeof fl);
memset(vis, 0, sizeof vis);
queue < int > q; int qwq = 0;
pren[t] = - 1; vis[s] = 1; q.push(s); val[s] = 0;
while(! q.empty()){
int u = q.front(); q.pop();
vis[u] = 0;
for(int i = hd[u]; i; i = e[i].nxt){
int v = e[i].to, d = e[i].d, w = e[i].w;
if(d and val[v] > val[u] + w){
val[v] = val[u] + w;
if(! vis[v]){
vis[v] = 1;
q.push(v);
}
fl[v] = min(fl[u], d);
pren[v] = u; pree[v] = i;
}
}
}
return pren[t] != - 1;
}
int ansv;
void MCMF(){
while(SPFA()){
ansv += fl[t] * val[t];
int x = t;
while(x != s){
e[pree[x]].d -= fl[t];
e[pree[x] ^ 1].d += fl[t];
x = pren[x];
}
}
}
signed main(){
n = rd(); m = rd(); s = N - 3, t = s + 1;
for(int i = 1; i <= n; ++i){
a[i] = rd();
add(s, i, 1, a[i]); add(i, s, 0, - a[i]);
add(s, i + n, 1, 0); add(i + n, s, 0, 0);
add(i, t, 1, 0); add(t, i, 0, 0);
}
for(int i = 1; i <= m; ++i){
int u = rd(), v = rd(), time = rd();
if(u > v)swap(u, v);
add(u + n, v, 1, time); add(v, u + n, 0, - time);
}
MCMF();
printf("%d", ansv);
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· .NET10 - 预览版1新功能体验(一)