const int N = 1e4 + 5;
const int M = 2e5 + 5;
int nxt[M], to[M], cap[M], adj[N], que[N], cur[N], lev[N];
int n, m, src, des, qr, T = 1;
inline void linkArc(int x, int y, int w)
{
nxt[++T] = adj[x]; adj[x] = T; to[T] = y; cap[T] = w;
nxt[++T] = adj[y]; adj[y] = T; to[T] = x; cap[T] = 0;
}
inline bool Bfs()
{
for (int x = 1; x <= n; ++x)
cur[x] = adj[x], lev[x] = -1;
// 初始化具体的范围视建图而定,这里点的范围为 [1,n]
que[qr = 1] = src;
lev[src] = 0;
for (int i = 1; i <= qr; ++i)
{
int x = que[i], y;
for (int e = adj[x]; e; e = nxt[e])
if (cap[e] > 0 && lev[y = to[e]] == -1)
{
lev[y] = lev[x] + 1;
que[++qr] = y;
if (y == des)
return true;
}
}
return false;
}
inline ll Dinic(int x, ll flow)
{
if (x == des)
return flow;
int y, delta; ll res = 0;
for (int &e = cur[x]; e; e = nxt[e])
if (cap[e] > 0 && lev[y = to[e]] > lev[x])
{
delta = Dinic(y, Min(flow - res, (ll)cap[e]));
if (delta)
{
cap[e] -= delta;
cap[e ^ 1] += delta;
res += delta;
if (res == flow)
break ;
//此时 break 保证下次 cur[x] 仍有机会增广
}
}
if (res != flow)
lev[x] = -1;
return res;
}
inline ll maxFlow()
{
ll res = 0;
while (Bfs())
res += Dinic(src, Maxn);
return res;
}
证明 对于单位网络,每条边最多被考虑一次,一轮增广的时间复杂度为 O ( m ) \mathcal O(m) O(m)。
假设我们已经完成了前 n \sqrt n n 轮增广,还需找到 d d d 条增广路才能找到最大流,每条增广路的长度至少为 n \sqrt n n。这些增广路不会在源点和汇点以外的点相交,因而至少经过了 d n d\sqrt n dn 个点, d ≤ n d \le \sqrt n d≤n,则至多还需增广 n \sqrt n n 轮,总时间复杂度 O ( m n ) \mathcal O(m\sqrt n) O(mn)。
每次寻找单位费用最小的增广路进行增广,直至图中不存在增广路为止。
设流量为 i i i 的时候最小费用为 f i f_i fi,假设初始网络上没有负圈, f 0 = 0 f_0 = 0 f0=0。
假设用 SSP \text{SSP} SSP 算法求出的 f i f_i fi 是最小费用,我们在 f i f_i fi 的基础上,找到一条最短的增广路,从而求出 f i + 1 f_{i + 1} fi+1,此时 f i + 1 − f i f_{i + 1} - f_i fi+1−fi 就是这条最短增广路的长度。
假设存在更小的 f i + 1 f_{i + 1} fi+1,设其为 f i + 1 ′ f_{i+1}' fi+1′,则 f i + 1 ′ − f i f'_{i + 1} - f_i fi+1′−fi 一定对应一个经过至少一个负圈的增广路。若残量网络中存在至少一个负圈,则可在不增加 s s s 流出的流量的情况下使费用减小,与 f i f_i fi 是最小费用矛盾。
综上, SSP \text{SSP} SSP 算法可以正确求出无负圈网络的最小费用最大流,设最大流为 F F F,总时间复杂度 O ( F n m ) \mathcal O(Fnm) O(Fnm)。
const int N = 5e3 + 5;
const int M = 1e5 + 5;
int nxt[M], to[M], cap[M], que[M], cst[M], adj[N], dis[N];
bool vis[N]; int n, m, src, des, ans, T = 1, qr;
inline void linkArc(int x, int y, int w, int z)
{
nxt[++T] = adj[x]; adj[x] = T; to[T] = y; cap[T] = w; cst[T] = z;
nxt[++T] = adj[y]; adj[y] = T; to[T] = x; cap[T] = 0; cst[T] = -z;
}
inline bool SPFA()
{
for (int x = 1; x <= n; ++x)
dis[x] = Maxn, vis[x] = false;
// 初始化具体的范围视建图而定,这里点的范围为 [1,n]
dis[que[qr = 1] = src] = 0;
for (int i = 1, x, y; i <= qr; ++i)
{
vis[x = que[i]] = false;
for (int e = adj[x]; e; e = nxt[e])
if (cap[e] > 0 && dis[y = to[e]] > dis[x] + cst[e])
{
dis[y] = dis[x] + cst[e];
if (!vis[y])
vis[que[++qr] = y] = true;
}
}
return dis[des] < Maxn;
}
inline int Dinic(int x, int flow)
{
if (x == des)
{
ans += flow * dis[des];
return flow;
}
vis[x] = true;
int y, delta, res = 0;
for (int e = adj[x]; e; e = nxt[e])
if (!vis[y = to[e]] && cap[e] > 0 && dis[y] == dis[x] + cst[e])
// vis 数组防止 dfs 在总费用为 0 的环上死循环
{
delta = Dinic(y, Min(flow - res, cap[e]));
if (delta)
{
cap[e] -= delta;
cap[e ^ 1] += delta;
res += delta;
if (res == flow)
break ;
}
}
return res;
}
inline int MCMF()
{
ans = 0;
int res = 0;
while (SPFA())
res += Dinic(src, Maxn);
return res;
}
证明 设最大匹配数为 x x x,则其覆盖的点数为 2 x 2x 2x,剩余点两两之间没有边。则最少需要增加 ∣ V ∣ − 2 x |V| - 2x ∣V∣−2x 条边才能将所有点覆盖,则最小边覆盖数为 x + ∣ V ∣ − 2 x = ∣ V ∣ − x x + |V| - 2x = |V| - x x+∣V∣−2x=∣V∣−x。
证明 只需证明独立集和点覆盖一一对应且互为关于 V V V 的补集即可。取一个点覆盖关于 V V V 的补集,若其不是一个独立集,则存在两点有公共边,不难发现这与点覆盖的定义矛盾。
证明 设最大匹配数为 x x x,即让匹配中的每条边都和其一个端点关联。 x x x 个点是足够的,否则若存在一条边未被覆盖,加入这条边能得到一个更大的匹配。 x x x 个点是必需的,因为匹配的 x x x 条边两两无公共点。
结论4 在有向无环图中, ∣ 最小点不相交路径覆盖 ∣ + ∣ 最大匹配 ∣ = ∣ V ∣ |最小点不相交路径覆盖|+|最大匹配|=|V| ∣最小点不相交路径覆盖∣+∣最大匹配∣=∣V∣。这里的最大匹配指的是,将原图每个点拆成入点和出点两点,对于有向边 x → y x \to y x→y,将 x x x 的入点向 y y y 的出点连边,在该二分图上求得的最大匹配。
// C 为二分图一侧的点数,src = 2 * C + 1
// 注意孤立点以及删去一条链可能会产生新的起点的情况
for (int x = 1; x <= C; ++x)
for (int e = adj[x]; e; e = nxt[e])
if (to[e] > C && to[e] < src && to[e] - C != x && cap[e ^ 1] > 0)
{
re[x].emplace_back(std::make_pair(to[e] - C, cap[e ^ 1]));
++lre[x];
sre[x] += cap[e ^ 1];
ind[to[e] - C] += cap[e ^ 1];
}
tis = 0;
for (int t = 1; t <= C; ++t)
{
for (int x = 1; x <= C; ++x)
while (sre[x] > ind[x])
{
++tis;
int u = x;
ans[u] = tis;
while (sre[u])
{
pir &v = re[u][lre[u] - 1];
int vid = v.first;
if (--v.second == 0)
re[u].pop_back(), --lre[u];
--sre[u];
u = vid;
ans[u] = tis;
--ind[u];
}
}
}
证明 只需证明二分图中匹配与原图中的路径覆盖一一对应且总和为 ∣ V ∣ |V| ∣V∣。对于每个没有匹配边的出点,我们都能构造一条路径,若其对应的入点存在匹配边,则将该匹配边加入该路径同时继续考虑该匹配边的出点直至其对应的入点不存在匹配边。得到的所有路径恰能覆盖所有点,且没有匹配边的出点和入点恰好能两两配对分别构成所有路径的起点和终点。
证明 设 m m m 为最大反链的元素个数, ∣ X ∣ = n |X| = n ∣X∣=n,只要证明偏序集 X X X 可被划分成 m m m 个链即可。
考虑用归纳法证明,当 n = 1 n = 1 n=1 时显然成立,下面证明 n > 1 n > 1 n>1 时的情形,设该有限偏序集中所有极小点构成的集合为 L L L,所有极大点构成的集合为 G G G,分两种情况讨论:
若存在最大反链 A A A,使得 A ≠ L A \not = L A=L 且 A ≠ G A \not = G A=G。构造:
A + = { x ∣ x ∈ X ∧ ∃ a ∈ A , a ≤ x } A − = { x ∣ x ∈ X ∧ ∃ a ∈ A , x ≤ a } A^+=\{x|x\in X \wedge \exist a\in A,a\le x\} \\ A^-=\{x|x\in X \wedge \exist a\in A,x \le a\} \\ A+={x∣x∈X∧∃a∈A,a≤x}A−={x∣x∈X∧∃a∈A,x≤a}
则容易得到 ∣ A + ∣ < ∣ X ∣ |A^+|<|X| ∣A+∣<∣X∣、 ∣ A − ∣ < ∣ X ∣ |A^-|<|X| ∣A−∣<∣X∣ 且 A + ∪ A − = X , A + ∩ A − = A A^+\cup A^- =X,A^{+}\cap A^-=A A+∪A−=X,A+∩A−=A,由归纳假设可将 A + A^{+} A+ 和 A − A^{-} A− 的 m m m 链划分拼接起来即可得到 X X X 的 m m m 链划分。若只存在反链 A = L A = L A=L 或 A = G A = G A=G,取极小元 x x x 和极大元 y y y 满足 x ≤ y x\le y x≤y( x , y x,y x,y 可相等),则由归纳假设 X − { x , y } X -\{x,y\} X−{x,y} 最大反链的元素个数为 m − 1 m - 1 m−1,存在 m − 1 m - 1 m−1 链划分,增加链 x ≤ y x\le y x≤y 可得到 X X X 的 m m m 链划分。
证明 设最长链的长度为 m m m,最小反链划分的数目为 M M M。
- 由于在任意一个反链中选取超过一个元素都违反链的定义,容易得到 m ≤ M m \le M m≤M。
- 考虑每次删去当前偏序集中的所有极小点,恰好删除 m m m 次能够将所有点删除,设第 i i i 次删点构成的极小点集合为 A i A_i Ai,则 A 1 , A 2 , … , A m A_1,A_2,\dots,A_m A1,A2,…,Am 构成一个反链划分,故有 M ≤ m M \le m M≤m。
综上所述, m = M m = M m=M,原命题得证。
inline bool Hungary(int x)
{
int y;
for (arc *e = adj[x]; e; e = e->nxt)
if (!mateR[y = e->to])
return mateR[y] = x, true;
for (arc *e = adj[x]; e; e = e->nxt)
{
if (vis[y = e->to] == tis)
continue ;
vis[y] = tis;
if (Hungary(mateR[y]))
return mateR[y] = x, true;
}
return false;
}
inline int maxMatch()
{
int cnt = 0;
for (int i = 1; i <= n; ++i)
{
++tis;
if (Hungary(i))
++cnt;
}
return cnt;
}
证明 设可行顶标下相等子图中的完美匹配为 M ′ M' M′,对于原二分图任意一组完美匹配 M M M,其边权和
val ( M ) = ∑ ( x , y ) ∈ M w ( x , y ) ≤ ∑ x ∈ X labx ( x ) + ∑ y ∈ Y laby ( y ) = val ( M ′ ) \text{val}(M) = \sum \limits_{(x,y)\in M}w(x,y) \le \sum\limits_{x\in X}\text{labx}(x) + \sum \limits_{y \in Y} \text{laby}(y) = \text{val}(M') val(M)=(x,y)∈M∑w(x,y)≤x∈X∑labx(x)+y∈Y∑laby(y)=val(M′)
故若 M ′ M' M′ 存在, M ′ M' M′ 即为最大权完美匹配。
typedef long long ll;
const int N = 405;
const int Maxn = 2e9;
int que[N], w[N][N], slacky[N];
int labx[N], laby[N], matex[N], matey[N], pre[N];
bool visx[N], visy[N];
int nl, nr, qr, n, m; ll ans;
inline bool Augment(int y)
{
if (matey[y])
{
que[++qr] = matey[y];
visx[matey[y]] = visy[y] = true;
return false;
}
else
{
while (y)
{
int x = pre[y];
matey[y] = x;
std::swap(matex[x], y);
}
return true;
}
}
inline void bfsHungary(int src)
{
for (int i = 1; i <= n; ++i)
{
pre[i] = 0;
visx[i] = visy[i] = false;
slacky[i] = Maxn;
}
visx[que[qr = 1] = src] = true;
while (1)
{
for (int i = 1, x; i <= qr; ++i)
{
x = que[i];
for (int y = 1; y <= n; ++y)
if (!visy[y])
{
int delta = labx[x] + laby[y] - w[x][y];
if (delta > slacky[y])
continue ;
pre[y] = x;
if (delta > 0)
slacky[y] = delta;
else if (Augment(y))
return ;
}
}
int nxt, delta = Maxn;
for (int y = 1; y <= n; ++y)
if (!visy[y] && slacky[y] < delta)
delta = slacky[y], nxt = y;
for (int i = 1; i <= n; ++i)
{
if (visx[i])
labx[i] -= delta;
if (visy[i])
laby[i] += delta;
else
slacky[i] -= delta;
}
qr = 0;
if (Augment(nxt))
return ;
}
}
int main()
{
read(nl); read(nr); read(m);
n = Max(nl, nr);
int x, y, z;
while (m--)
{
read(x); read(y); read(z);
CkMax(w[x][y], z);
CkMax(labx[x], z);
}
for (int i = 1; i <= n; ++i)
bfsHungary(i);
for (int i = 1; i <= n; ++i)
ans += w[i][matex[i]];
put(ans), putchar('\n');
for (int i = 1; i <= nl; ++i)
put(w[i][matex[i]] ? matex[i] : 0), putchar(' ');
}
rkx = rky = matx = maty = dict()
for x in prfx.keys():
for i in range(len(prfx[x])):
rkx[(x, prfx[x][i])] = i
for y in prfy.keys():
for i in range(len(prfy[y])):
rky[(y, prfy[y][i])] = i
for x in prfx.keys():
prfx[x].reverse()
list = [x for x in prfx.keys()]
while len(list) > 0:
x = list.pop()
while len(prfx[x]) > 0:
y = prfx[x].pop()
if y not in maty:
maty[y] = x
matx[x] = y
break
elif rky[(y, x)] < rky[(y, maty[y])]:
del matx[maty[y]]
list.append(maty[y])
maty[y] = x
matx[x] = y
break
证明 记 y = y x y = y_x y=yx,假设 ( x , y ) (x,y) (x,y) 是有效的且 M M M 不包含 ( x , y ) (x,y) (x,y)。
设 M M M 中与 y y y 匹配的是 x ′ x' x′,由算法流程 r k Y [ y ] [ x ′ ] < r k Y [ y ] [ x ] rk_Y[y][x']
rkY[y][x′]<rkY[y][x] (要么 x x x 匹配后被 x ′ x' x′ 替代,要么 x ′ x' x′ 先匹配后无法被 x x x 替代)。由有效配对的定义,一定存在稳定匹配 M ′ M' M′ 包含 ( x , y ) (x,y) (x,y)。
记 M ′ M' M′ 中与 x ′ x' x′ 匹配的是 y ′ y' y′,能够得到匹配 M M M 的条件是 r k X [ x ′ ] [ y ] < r k X [ x ′ ] [ y ′ ] rk_X[x'][y] < rk_X[x'][y'] rkX[x′][y]<rkX[x′][y′],则由于 ( x ′ , y ) (x',y) (x′,y) 的存在, M ′ M' M′ 是不稳定的,与假设矛盾。
证明 设 ∣ V ( G ) ∣ = n |V(G)|=n ∣V(G)∣=n,考虑数学归纳法。
首先, n ≤ 3 n\le 3 n≤3 时,命题显然成立。
根据归纳法,假设对于 n − 1 n - 1 n−1 的命题成立。
不妨只考虑 Δ ( G ) \Delta(G) Δ(G)-正则图,因为对于非正则图来说,可以看作在正则图里删去一些边构成的,而这一过程并不会影响结论。
对于任意不是完全图也不是奇圈的正则图 G G G,任取其中一点 v v v,考虑子图 H = G − v H = G - v H=G−v,由归纳假设知 χ ( H ) ≤ Δ ( H ) ≤ Δ ( G ) \chi(H)\le \Delta(H) \le \Delta(G) χ(H)≤Δ(H)≤Δ(G),接下来我们只需证明在 H H H 中插入 v v v 不会影响结论即可。
若 Δ ( H ) < Δ ( G ) \Delta(H) < \Delta(G) Δ(H)<Δ(G),无需再做证明,我们只考虑 Δ ( H ) = Δ ( G ) \Delta(H) = \Delta(G) Δ(H)=Δ(G) 的情况。
令 Δ = Δ ( G ) \Delta = \Delta(G) Δ=Δ(G),设 H H H 染的 C C C 种颜色分别为 c 1 , c 2 , … , c Δ c_1, c_2, \dots, c_{\Delta} c1,c2,…,cΔ, v v v 的 Δ \Delta Δ 个邻接点为 v 1 , v 2 , … , v Δ v_1, v_2, \dots, v_{\Delta} v1,v2,…,vΔ。若 v v v 的邻接点个数不足 Δ \Delta Δ 个或存在任意两点颜色相同,同样无需再做证明。
设所有在 H H H 中染成 c i c_i ci 或 c j c_j cj 的点以及它们之间的所有边构成子图 H i , j H_{i,j} Hi,j。不妨假设任意 2 个不同的点 v i , v j v_i,v_j vi,vj 一定在 H i , j H_{i,j} Hi,j 的同一个连通分量中,否则若在两个连通分量中的话,可以交换其中一个连通分量所有点的颜色,从而使 v i , v j v_i,v_j vi,vj 颜色相同,即能有多余的颜色对 v v v 进行染色,无需再做证明。
这里的交换颜色指的是若图中只有两种颜色 a , b a,b a,b,那么把图中原来染成颜色 a a a 的点全部染成颜色 b b b,把图中原来染成颜色 b b b 的点全部染成颜色 a a a。
我们设上述连通分量为 C i , j C_{i,j} Ci,j,取出 C i , j C_{i,j} Ci,j 中一条路径记作 P i , j P_{i,j} Pi,j,则恒有 C i , j = P i , j C_{i,j} = P_{i,j} Ci,j=Pi,j。因为 v i v_i vi 在 H H H 中的度为 Δ − 1 \Delta-1 Δ−1,所以 v i v_i vi 在 H H H 中的邻接点颜色一定两两不同,否则可以给 v i v_i vi 染别的颜色,从而和 v v v 的其他邻接点颜色重复,所以 v i v_i vi 在 C i , j C_{i,j} Ci,j 中邻接点数量为 1。若 C i , j ≠ P i , j C_{i,j} \not = P_{i,j} Ci,j=Pi,j,设在 C i , j C_{i,j} Ci,j 中从 v i v_i vi 开始沿着 P i , j P_{i,j} Pi,j 遇到的第一个度数大于 2 的点为 u u u,注意到 u u u 的邻接点最多只用了 Δ − 2 \Delta - 2 Δ−2 种颜色,所以 u u u 可以重新染色,从而使 v i , v j v_i,v_j vi,vj 不连通。
沿用这一技术,我们可以证明对于 3 个不同的点 v i , v j , v k v_i,v_j,v_k vi,vj,vk, V ( C i , j ) ∩ V ( C j , k ) = { v j } V(C_{i,j})\cap V(C_{j,k}) = \{v_j\} V(Ci,j)∩V(Cj,k)={vj}。假设存在 w ∈ V ( C i , j ) ∩ V ( C j , k ) w \in V(C_{i,j})\cap V(C_{j,k}) w∈V(Ci,j)∩V(Cj,k),若 w ≠ v j w \not = v_j w=vj,则 w w w 必被染色为 c j c_j cj,且恰有两个被染色为 c i c_i ci 的邻接点和两个被染色为 c j c_j cj 的邻接点,注意到 w w w 的邻接点最多只用了 Δ − 2 \Delta - 2 Δ−2 种颜色,所以 w w w 同样可以重新染色。
若 v v v 的邻接点两两相邻,则必有 Δ = n \Delta = n Δ=n,即 G G G 为完全图。否则不妨设 v 1 , v 2 v_1,v_2 v1,v2 不相邻,在 C 1 , 2 C_{1,2} C1,2 取 v 1 v_1 v1 的邻接点 w w w,交换 C 1 , 3 C_{1,3} C1,3 中的颜色,则 w ∈ V ( C 1 , 2 ) ∩ V ( C 2 , 3 ) w \in V(C_{1,2})\cap V(C_{2,3}) w∈V(C1,2)∩V(C2,3),与上述结论矛盾。
至此命题证明完毕。
证明 对强连通分量个数归纳,若新增的强连通分量拓扑序最大,则所有连向其的边同向。
证明 对点数 n ( n ≥ 3 ) n(n\ge3) n(n≥3) 归纳,若新增的点连向原有 n n n 个点的所有边均同向,则这 n + 1 n + 1 n+1 个点不构成强连通分量,否则总可以找到 n n n 个点中的两个点,使得它们在哈密顿回路上相邻且连向新增点的方向相反。
证明 因为属于不同强连通分量间的点均有连边,由 性质2 取每个强连通分量中的哈密顿回路相连即可。
证明 考虑对点数 n n n 归纳,由 性质1,删除第 n n n 个点后原图变为若干个强连通分量的链状图,由归纳条件,每个强连通分量均可以构造出不超过点数的简单环,且第 n n n 个点一定有指向拓扑序最小的强连通分量的边,拓扑序最大的强连通分量一定有指向 n n n 的边,不难得到构造长度不超过 n n n 的简单环的方案。
证明 考虑证明其逆否命题,由 性质1,仅需证明 u u u 所在强连通分量拓扑序大于 v v v 所在强连通分量拓扑序的情况,此时显然 v v v 的出度大于 u u u 的出度。
证明 其必要性显然,因为任取点数为 k k k 的导出子图都满足该条件。考虑证明其充分性,构造一个所有边均是大点连向小点的竞赛图,则其 s i ′ = i − 1 s_i'=i-1 si′=i−1,上述不等式可变形为 ∑ i = 1 k s i ≥ ∑ i = 1 k s i ′ \sum\limits_{i = 1}^{k}s_i\ge \sum\limits_{i = 1}^{k}s_i' i=1∑ksi≥i=1∑ksi′,现在尝试通过不断调整该图,使得每个不等式均能取等号,每次操作如下:
- 找到第一个 x x x 使得 s x > s x ′ s_x > s_x' sx>sx′。
- 在 x x x 后找到第一个 z z z 使得 s z < s z ′ s_z < s_z' sz<sz′
- 因而有 s z ′ > s z ≥ s x > s x ′ s_z'>s_z\ge s_x > s_x' sz′>sz≥sx>sx′,即 s z ′ − s x ′ ≥ 2 s_z' - s_x'\ge 2 sz′−sx′≥2,此时必存在点 y y y,使得边 z → y z \to y z→y 和 y → x y \to x y→x 存在,将这两条边反向,则 s z ′ s'_z sz′ 减少 1, s x ′ s_x' sx′ 增加 1, s y ′ s_y' sy′ 不变,原不等式仍成立。