网络流5:割与流的转化
网络流5:割与流的转化
这个坑挖了很久了,顺手填一下吧。至于网络流 3 和网络流 4,只能说祝它们好运。
或许也可以称之为模拟最大流/模拟最小割?
隔壁还有模拟费用流呢,自然也可以有模拟最大流。
这算一类小 trick 了吧。
当我们遇到一道流/割问题时,不一定非得用流/割的思路来做。我们可以观察图的性质,比如一些局部最优解(贪心)、可行解结构(贪心或 dp),并使用其他算法来进行模拟。
转化方式因题而异,让我们结合题目来试试吧。
例题I AT_arc125_e 模拟最大流
题面
有 \(N\) 种编号为 \(1\) 到 \(N\) 的糖果。第 \(i\) 种糖果有 \(A_i\) 个。
有 \(M\) 个编号为 \(1\) 到 \(M\) 的孩子。现在要给这些孩子分糖果。分配时需满足以下所有条件:
- 孩子 \(i\) 对于任意一种糖果,最多只能拿 \(B_i\) 个。
- 孩子 \(i\) 拿到的糖果总数不超过 \(C_i\)。
在满足上述条件的前提下,求能分给孩子们的糖果总数的最大值。
Solution
本题不难想到最大流建图。具体的:
- 建立源点 \(S\) 和汇点 \(T\)。
- 建立糖果点 \([1,n]\),由源点向每个糖果点连容量为 \(A_i\) 的边,约束糖果总数。这类边称之为第一类边;
- 建立孩子点 \([n+1,n+m]\),每个糖果点向孩子点 $n+i $ 连 \(B_i\) 的边约束孩子拿到每类最多的糖果数量。这类边称之为第二类边;
- 每个人向汇点连边,容量 \(C_i\),约束人能选的总量。这类边称之为第三类边。
然后你的复杂度就炸啦。
就在这时,注意本章名称,不难发现我们可以尝试将最大流转化。
仔细观察性质,发现很多边是冗余的,大量的边容量相同,大量的点是对称的。我们从这个方向考虑。
什么东西会破坏点与边之间的等价呢?答案是边的流量。但是边的流量会随着方案改变而改变,看起来是个很 Hard 的问题,不太像能轻易维护的样子。有什么方法可以做到边不与流量、方案有关,而只与边本身有关呢?
最小割!
从最小割考虑。我们发现一个割的方案是:一些糖被割到 \(T\) 侧,他们的第一类边被割掉;一些孩子被分到 \(S\) 侧,他们的第三类边被割掉;此外,还有一些中间横跨 \(S\) 侧和 \(T\) 侧的第二类边被割掉。
注意到第二类边与只孩子有关,那我们贪心的选糖。若有 \(i\) 个糖连向 \(T\) 侧,贪心地选择 \(i\) 个最小的 \(A_i\) 即可;对于每个小孩 \(j\),如果连 \(S\) 侧代价为 \(C_j\),如果连 \(T\) 侧代价为 $ (n-i) B_j $。
接下来枚举 \(i\),并按 $ C_j/B_j $ 贪心取小孩即可。
代码
void mian()
{
read(n);read(m);
LL res=1ll*INF*INF,sa=0,sb=0,sc=0;
For(i,1,n) read(a[i]);
For(i,1,m) read(p[i].first);
For(i,1,m) read(p[i].second),sc+=p[i].second;
sort(a+1,a+1+n);
sort(p+1,p+1+m,[](const auto &p1,const auto &p2){return p1.second*p2.first>p1.first*p2.second;});
int r=1;
For(i,0,n)
{
sa+=a[i];
while(p[r].second>=(n-i)*(p[r].first)&&r<=m) sb+=p[r].first,sc-=p[r].second,r++;
tomin(res,sa+sb*(n-i)+sc);
}
printf("%lld\n",res);
}
例题II [CSP-S 2021] 交通规划 模拟最小割
好题确实是好题,就是有点超纲。
题面
太长了,略。
可以直接点题目链接。
part 1
不难想到最小割建图:
超级源点向所有白点连代价为正无穷的边;超级汇点向所有黑点连代价为正无穷的边,那么题目就是让我们求一个最小割。
直接做可以获得高额的 60 分。
part 2
注意到有一个 \(k \le 2\) 的性质。
首先,如果 \(k=0\),那直接 puts("0") 跑路。
对于 \(k=2\),我们发现原图是平面图,而平面图最小割等于对偶图的最短路。如果我们将附加边看成无限长的射线,将原本的一个无限面划分成了多个无限面,那么答案就是两个无限面之间的最短路。
每次跑 Dijkstra 即可,复杂度 $ O(Tnm \log(nm)) $
到这一步为止,其实已经与最小割没关系了。
part 3
(接下来复杂度分析省略多测)
对于 $k\ge 2 $ 的情况,由此启发,我们想到试着刻画最小割的形态,推广对偶图最短路。最基本的想法就是不同颜色的点两两配对,随后配对的点之间连最短路。
我们可以直接对每个起始点跑一个单源最短路,就能 \(O(1)\) 计算配对点之间的最短路了。预处理复杂度 $ O(k nm\log (nm)) $。
单这一点是不够的,直接枚举配对方案需要指数复杂度,我们还需要更多性质。通过简单的手玩发现,似乎这些最短路是没有交叉的。人类智慧地思考,如果有交叉,交换配对点会获得不劣的方案。
详细证明的话,OI不需要证明,限于篇幅原因,在这里就不讲证明了。这是一份别的题解的证明,题解原文为参考资料 5。
也就是说,将点顺时针排序后,配对点不交。观察到这一点后,配对方案就变成了一个平凡的环上区间 dp,这部分复杂度 \(O(k^3)\)。
合计复杂度 $ O(knm\log(nm) + k^3) $
代码
感觉本题更难的部分是代码细节。可以参考注释。
代码注释部分由 AI(deepseek)添加,人工润色了一下。
// 链式前向星存图
struct Edge{
int v,nx,w;
}e[M];
int hd[N],ecnt;
void add(int u,int v,int w){e[++ecnt]={v,hd[u],w};hd[u]=ecnt;}
void __add(int u,int v,int w){add(u,v,w);add(v,u,w);}
// 我们直接建对偶图
// 外围环节点编号 1 ~ N (N = 2*(n+m)),内部小方格节点从 N+1 开始
// 注意:这里采用“边编号 = 节点编号”的自然方式:
// 先加边 (1, N),然后加 (1,2), (2,3), ..., (N-1, N)
// 这样外围边 i 恰好穿过节点 i,因此附加点 id 可以直接当作节点 id 使用
int init()
{
int Q;
read(n),read(m);read(Q);
int N=n+m<<1; // 外围环的节点数
// 建外围环:边 1 连接 (1, N),边 2 连接 (1,2),边 3 连接 (2,3)...
__add(1,2*(n+m),0);
For(i,1,N-1) __add(i,i+1,0);
// 内部节点编号函数:第 i 行第 j 列的小方格对应节点 id
auto f=[](int x,int y){return (x-1)*(m-1)+y+(n+m)*2;};
// 读入水平方向的边(横向边),连接内部小方格或外围环
For(i,1,n-1) For(j,1,m){
int w;read(w);
int id=f(i,j);
if(j==1) __add(N-i,id,w); // 最左侧,连接到外围环节点 N-i
else if(j==m) __add(m+i,id-1,w); // 最右侧,连接到外围环节点 m+i
else __add(id-1,id,w); // 内部小方格之间
}
// 读入垂直方向的边(纵向边)
For(i,1,n) For(j,1,m-1){
int w;read(w);
int id=f(i,j),id2=f(i-1,j);
if(i==1) __add(j,id,w); // 最上方,连接到外围环节点 j
else if(i==n) __add(m+n+m-j,id2,w);// 最下方,连接到外围环节点 m+n+m-j
else __add(id,id2,w); // 内部小方格之间
}
return Q;
}
// 附加点信息
struct Node{
int w,id,col; // w: 附加点代价,id: 所在外围边编号,col: 颜色(0/1)
}a[N];
int sc,ec; // sc: 起点数量,ec: 终点数量
int st[N],ed[N],rk[N]; // st/ed 存起点/终点的外围边编号(1~2*N),rk 存每个关键点在环上的顺序编号(1~sc+ec)
// 扫描环上相邻点对,识别颜色变化,确定起点和终点
// 利用 a[1] 作为参考色,统一颜色变化方向,避免首尾特殊处理
void check(int x,int y)
{
// 若 a[x] 与参考色相同,a[y] 不同,则 a[x] 为起点
if(a[x].col==a[1].col&&a[y].col!=a[1].col) st[++sc]=a[x].id,rk[a[x].id]=sc+ec;
// 若 a[x] 与参考色不同,a[y] 相同,则 a[x] 为终点
else if(a[x].col!=a[1].col&&a[y].col==a[1].col) ed[++ec]=a[x].id,rk[a[x].id]=sc+ec;
}
// 单源最短路(Dijkstra)
LL dis[N];bool vis[N];
priority_queue <pair <LL,int> > pq;
void dij(int st)
{
memset(dis,0x3f,sizeof(dis));
memset(vis,0,sizeof(vis));
while(!pq.empty()) pq.pop();
dis[st]=0;pq.push({0,st});
while(!pq.empty()){
int x=pq.top().second;pq.pop();
if(vis[x]) continue;vis[x]=1;
for(int i=hd[x];i;i=e[i].nx){
int v=e[i].v,w=e[i].w;
if(dis[v]>dis[x]+w){
dis[v]=dis[x]+w;
pq.push({-dis[v],v}); // 取负实现小根堆
}
}
}
}
// 区间DP:环上起点与终点一一配对,求最小总代价
// 传入参数 k 为总关键点数(即 sc+ec,也等于 2*sc)
LL cost[K][K]; // cost[i][j] 表示环上第 i 个关键点到第 j 个关键点的最短路代价
LL F[K][K]; // F[l][r] 表示区间 [l, r] 内所有点完成匹配的最小代价
LL dp(int k)
{
int n=k<<1; // 将环拆成二倍链,长度为 2*k
// 将二倍链下标映射回原始环上的 1~k
auto f=[&](const int x){return (x-1)%k+1;};
// 初始化长度为 2 的区间
For(i,1,n-1) F[i][i+1]=cost[f(i)][f(i+1)];
// 区间 DP,长度从 4 开始,每次增加 2
for(int len=4;len<=k;len+=2){
For(l,1,n){
int r=l+len-1;if(r>n) break;
// 情况1:l 和 r 直接配对,内部区间 [l+1, r-1] 已完成匹配
F[l][r]=F[l+1][r-1]+cost[f(l)][f(r)];
// 情况2:枚举分割点 mid,将区间分成两部分
for(int mid=l+1;mid<=r;mid+=2){
tomin(F[l][r],F[l][mid]+F[mid+1][r]);
}
}
}
// 在二倍链上找一个长度为 k 的区间,取最小值
LL res=INF;
For(i,1,k) tomin(res,F[i][i+k-1]);
return res;
}
void solve()
{
int N=n+m<<1;
// 多测不清空,爆零两行泪
For(i,1,N<<1) e[i].w=0;
sc=ec=0;
memset(cost,0x3f,sizeof(cost));
memset(F,0x3f,sizeof(F));
read(k);
// 附加点,并直接修改对应外围边的权值
// 外围边 id 对应有向边 (id<<1)-1 和 id<<1
For(i,1,k)
read(a[i].w),read(a[i].id),read(a[i].col),
e[(a[i].id<<1)-1].w=e[a[i].id<<1].w=a[i].w;
// 按外围边编号升序排序,保证扫描顺序为环上的顺时针顺序
sort(a+1,a+1+k,[](const Node &x,const Node &y){
return x.id<y.id;
});
// 扫描环上相邻点对,识别起点和终点
check(k,1); // 检查最后一个和第一个(环的闭合处)
For(i,1,k-1) check(i,i+1);
if(sc==0) return puts("0"),void(); // 没有颜色变化,无需割边
// 对每个起点跑一次最短路,得到到所有终点的距离
For(i,1,sc){
dij(st[i]);
For(j,1,ec){
// cost 数组下标使用 rk 值(环上顺序编号)
cost[rk[st[i]]][rk[ed[j]]]=cost[rk[ed[j]]][rk[st[i]]]=dis[ed[j]];
}
}
// 区间 DP 求最小匹配代价
LL res=dp(sc+ec);
printf("%lld\n",res);
}
bool Med;
signed main()
{
int Test=init();
while(Test--) solve();
return 0;
}
总结
其实一般模拟最大流更多是转化为最小割来做,毕竟流量太难维护了。
而模拟最小割则是通过割本身的性质,比如说最优解形式,或是对偶图最短路来做。
好像就是重复了一遍开头的介绍。
参考资料/其他推荐
1 其他模拟最大流题目。
2 AT_arc125_e 题解
3 AT_arc125_e 题解
4 交通规划题解
5 交通规划题解
6 交通规划题解

浙公网安备 33010602011771号