『Record』网络流
经典模型总结
最小割点
即问题求解删点的贡献为 \(w_i\) 下最小割。
经典拆点,考虑将点拆成 \(u_{in},u_{out}\),连 \(u_{in} \xrightarrow{w_u} u_{out}\)。
而对于原图边 \((u,v)\),连 \(u_{out} \xrightarrow{+\infty} v_{in}\) 。
集合划分模型
有一个经典的模型是这样的,给定 \(E,c,a,b\),求解:
其中 \(x_i=0 /1\),而 \(\overline{x_i}\) 为取反操作。
考虑建模,首先对于任意 \(i\) ,连 \(S \xrightarrow{a_i} i \xrightarrow{b_i} T\)。
对于 \((u,v) \in E\),连 \(u \longleftrightarrow v\) 流量为 \(c_{u,v}\)。
一些奇怪的问题:
-
对于存在 \(a_i,b_i\) 有负数的情况,考虑由于 \(a_i,b_i\) 必定会有一个被选择,则考虑给 \(a_i,b_i\) 同时加上一个极大值,最后贡献减去即可。
-
对于要求限制为 \(x_u=0 \ \text{and} \ x_v=1\) 对式子有 \(c_{u,v}\) 的贡献,此时连单向边即可。
有个变式:
同理的,不过考虑割掉的边表示不选:
\(S \xrightarrow{c_{i,j}} (i,j),S \xrightarrow{a_i} i,S \xrightarrow{a_j} j\)
\((i,j)^{\prime}\xrightarrow{c_{i,j}} T,i \xrightarrow{b_i} T ,j \xrightarrow{b_j} T\)
\((i,j) \xrightarrow{+\infty} i,(i,j) \xrightarrow{+\infty} j,i \xrightarrow{+\infty} (i,j)^{\prime},j\xrightarrow{+\infty} (i,j)^{\prime}\)
这是不难理解的,可以手玩一下。
最大权闭合子图
只讲建模,不讲证明,因为容易手玩。
\(S \xrightarrow{a_i} i,a_i\geq 0\)
\(i \xrightarrow{-a_i} T,a_i< 0\)
\(u \xrightarrow{+\infty} v,(u,v) \in E\)
答案即为 \(\sum\limits_{a_i\geq 0} a_{i}-c\),其中 \(c\) 为最小割。
有负环的费用流
考虑类比上下界费用流,先转化为无源汇,考虑钦定负费用边必定流满,此处运用到网络流反悔的特殊性质。
同时删除原边,加入反边,将负费用取去绝对值,最后考虑加入虚源汇点,同理上下界建模即可。
上下界只是应定流满下界,而此处只是应定直接流满,但是又不能直接跑费用流,因为仍然存在负环,所以直接考虑建出反边跑。
注意,要在虚源汇,实源汇都跑一遍,这个和上下界最大/小流同理。
最大费用最大流
费用取反,然后跑有负环的费用流即可。
对偶问题
对于有些问题,限制在点上,而贡献在边上,可以考虑使用网络流解决。
而对于限制在边上,而贡献在点上的问题,大多数不好建模,考虑通过线性规划对偶解决。
这类问题没有统一的分析方式,对于不同的题目将有不同的求法。
而对偶则是指求解一类形如如下的问题:
这类问题等价于求解:
但是对偶后构造方案大多数情况比较麻烦,可能需要具体到题目。
网络流24题
P1251 餐巾计划问题
拆点,分别维护干净的毛巾和脏的毛巾。
建模如下:
\(S \xrightarrow{(r_i,0)} i,i^{\prime} \xrightarrow{(r_i,0)} T,S \xrightarrow{(+\infty,p)} i^{\prime}\)
\(i \xrightarrow{(+\infty,f)} (i+m)^{\prime},i \xrightarrow{(+\infty,s)} (i+n)^{\prime}\)
如果用了原始对偶优化复杂度就是 \(O(n^2+fn\log n)\)。
P2754 [CTSC1999] 家园 / 星际转移问题
飞船停靠站点与时间挂钩,考虑运用分层图思想。
枚举答案 \(t\),建模:
设 \((x,y)\) 表示时间在 \(x\) 的点 \(y\)。
\(S \xrightarrow{+\infty} (i,0),(i,j) \xrightarrow{+\infty} (i+1,j)\)
\((i,u) \xrightarrow{h_j} (i+1,v)\) ,其中 \(u,v\) 是 \(i\) 时刻到 \(i+1\) 时刻 \(j\) 号飞船的边上两点。
可以在答案增量的后加边然后在残余网络上跑最大流。
复杂度?。
P2756 飞行员配对方案问题
二分图最大匹配模板。
P2761 软件补丁问题
一个bug 有两种状态,出现或者没出现,状压每种状态,然后 dij 转移即可。
P2762 太空飞行计划问题
将实验和仪器都看成是点,一个实验向需要的一起连边,就是一个最大权闭合子图问题。
P2763 试题库问题
经典建模:
\(S \xrightarrow{1} i,i\xrightarrow{1}n+j,n+i\xrightarrow{p_i} T\)
其中 \(i\xrightarrow{1}n+j\) 是第 \(i\) 个试题选择 \(j\) 类型。
构造方案判流满否。
P2764 最小路径覆盖问题
DAG 不交最小链覆盖。
拆入点和出点,对于原图中边 \((u,v)\) 连\(u_{out} \xrightarrow{+\infty} v_{in}\)。
答案即为 \(n-m\),其中 \(n\) 为点数,\(m\) 为最大匹配。
扩展:
-
Dilworth 定理,偏序集中,最长反链等于最小链覆盖。
-
DAG 有交最小链覆盖,按照以传递闭包建出的邻接表求不交最小链覆盖即为所求。
-
二分图中的最小点覆盖等于最大匹配。
-
二分图中最小边覆盖覆盖等于最大独立集等于点数减去最小点覆盖。
P2765 魔术球问题
枚举答案 \(i\) ,对于 \(i+j=a^2(i>j)\) ,由于从小到大放,连 \(i\rightarrow j\),求最小链覆盖若超过 \(n\) 则 \(i-1\) 为答案。
构造方案同上。
P2766 最长不下降子序列问题
第一问水。
不能发现一个性质,设 \(f_i\) 表示以 \(i\) 为结尾的 LIS 长度。在我选择的子序列作为答案中,其中的一个长度为 \(s\) 的子序列上,两个相邻的元素 \(x,y\) 必然满足\(f_x=f_y+1(y<x)\)。
那么可以将问题转化为此类 \(x,y\) 连 \(y \rightarrow x\),求最多不交 \(k\) 长链,同时每个点 \(i\) 只能最为链上第 \(p_i\) 个。
建模:
\(S \xrightarrow{1} i(f_i=1),i\xrightarrow{1}j(f_j=f_i+1,a_i\leq a_j),i\xrightarrow{1} T(f_i=s)\)
跑最大流即为所求。
第三问同理的,只是 \(S \xrightarrow{+\infty} 1,n \xrightarrow{+\infty} T(f_n=s)\)
注意特判 \(n=1\)。
P2770 航空路线问题
不难发现要求即为 \(1_{out}\) 和 \(n_{in}\) 的度数 \(\leq 2\),其余点度数 \(\leq 1\)。
直接建模跑最大费用最大流即可,由于没环,直接反权值跑即可。
构造方案直接看是否满流即可。
P2774 方格取数问题
黑白染色,直接求解二分图最大权独立集即可。
P3254 圆桌问题
二分图多重匹配模板题。
类比二分图最大匹配的建模方式:
\(S \xrightarrow{r_i} i,i \xrightarrow{1} j+n,j+n \xrightarrow{c_j} T\)。
P3355 骑士共存问题
黑白染色,互相攻击点连边,跑二分图最大独立集即可。
P3356 火星探险问题
拆点 \(in,out\),点权转边权,对于有石子的点 \(u_{in} \xrightarrow{(1,1)} u_{out},u_{in} \xrightarrow{(n-1,0)} u_{out}\)。
对于没有石子的点 \(u_{in} \xrightarrow{(n,0)} u_{out}\)。
没环,最大费用最大流,输出方案记录一个点向下右的流量。
P3358 最长 k 可重区间集问题
这题放在 P3357 之前,原因是可以理解为后者是前者的加强版。
考虑用将限制转化,考虑一个点可以至多被 \(k\) 个区间交,那么考虑给 \(k\) 个区间分组,每组中区间无交则为充要条件。
应定 \(k\) 组可以直接建模设置流量上界 \(k\),而现在是容易建模的,拆区间为两点 \(L_{i},R_{i}\),设其左右端点为 \(l_i,r_i\):
\(S \xrightarrow{(1,0)}L_i,R_i\xrightarrow{(1,0)}T,T\xrightarrow{(k,0)} T^{\prime},L_i \xrightarrow{(1,r_i-l_i)}R_i\)
\(R_{i} \xrightarrow{(1,0)}L_j(r_i\leq l_j)\)
这样建模 \(m=O(n^2)\) 直接跑肯定不够优秀。
考虑优化:
不妨将所有 \(L_i,R_i\) 离散化拍到数列 \(x={1,\dots,p}\) 上。
建模:
\(S \xrightarrow{(k,0)}1,p \xrightarrow{(k,0)}T,L_i \xrightarrow{(1,r_i-l_i)}R_i\)
\(i \xrightarrow{(k,0)} i+1(i\in[1,p))\)
\(m=O(n)\),最大费用最大流 \(O(nmk)\)。
P3357 最长 k 可重线段集问题
可能发现就是 \(x\) 维做最长 \(k\) 可重区间集。
和 P3358 不同的是当存在一个线段平行于 \(y\) 轴,则其为 \([x,x]\) 闭区间。
考虑拆点,\(x_{in},x_{out}\)
建模:
\(S \xrightarrow{(k,0)}1_{in},p_{out} \xrightarrow{(k,0)}T,L_{i_{out}} \xrightarrow{(1,w_i)}R_{i_{in}}(L_i\not=R_i),L_{i_{in}} \xrightarrow{(1,w_i)}R_{i_{out}}(L_i=R_i)\)
\(i_{in} \xrightarrow{(k,0)} i_{out}(i\in[1,p])\)
\(i_{out} \xrightarrow{(k,0)} (i+1)_{out}(i\in[1,p))\)
\(m=O(n)\),最大费用最大流 \(O(nmk)\)。
P4009 汽车加油行驶问题
和网络流没啥关系。
设 \(f_{i,j,k}\) 表示走到 \((i,j)\) 剩下 \(k\) 个可行走单位,dij 暴力转移即可。
P4011 孤岛营救问题
由于钥匙数量很少,状压 \(O(nm 2^{P})\)。
P4012 深海机器人问题
类比 P3356 即可。
P4013 数字梯形问题
经典拆点,点边容量为 \(1\) 表示点有限制不交,边间容量为 \(1\) 表示边有限制不交,否则容量赋为 \(+\infty\)。
然后跑最大费用最大流即可,无环。
P4014 分配问题
二分图最小/大权匹配。
P4015 运输问题
同上。
P4016 负载平衡问题
类似上下界网络流,算出 \(avg\),若 \(a_i<avg\),连 \(S\xrightarrow{(avg-a_i,0)}i\),若 \(a_i>avg\),连 \(i\xrightarrow{(a_i-avg,0)}T\)
相邻边连容量为 \(+\infty\) 费用为 \(1\) 的边。
例题
[P2805 NOI2009] 植物大战僵尸
考虑将保护关系表示为,选什么之前必定要选什么,这和选一个点之前一定要右侧点限制同理。
如果选 \(u\) 前必须选 \(v\) 那么连 \(u \rightarrow v\),然后将环去掉后跑最大权闭合子图即可。
P5192【模板】有源汇上下界最大流
模板题?
建模:
\(S \xrightarrow{[0,D_i]} i,i\xrightarrow{[L_{i,k_i},R_{i,k_i}]} n+k_i,n+i \xrightarrow{[G_i,+\infty]} T\)
直接跑上下界最大流即可。
Florida
题意:给定一个有向图,\(n\) 个点,\(m\) 条边,你需要给每个点定权值 \(val_u \in[1,n]\),对于每条边 \(u \rightarrow v\),满足 \(val_u<val_v\),最小化 \(\sum\limits_{(u,v) \in E} val_v-val_u\),\(n \leq 300,m \leq 1500\)。
首先把已知限制写出来:
注意这是一个对偶形式,限制在边上,而贡献在点,难搞。
考虑对偶过去,把限制拍到点上,贡献搞到边上后尝试网络流解决。
对偶:
类似运用上下界的思路,考虑补流,在补流后的图上跑最大费用最大流,这就是答案。
对于构造方案,原限制类似差分约束,对于一条被流过的边,根据互补松弛性定理可以得出,这条边对应的原问题限制一定满足 \(val_v-val_u=1\),按照上述所有限制建差分约束模型即可求得一组解。
#pragma GCC target("sse,sse2,sse3,ssse3,sse4,popcnt,abm,mmx,avx,avx2")
#pragma GCC optimize("Ofast","unroll-loops","inline")
#include<bits/stdc++.h>
#define ll long long
//#define int ll
#define pb push_back
#define pii pair<int,int>
#define MP make_pair
#define v first
#define w second
using namespace std;
const int N=2e5+20,M=2e5+20,mod=998244353,Inf=1e9;
struct Edge{int v,c,w;};
namespace mcmf{
int n,s,t,ecnt,cur[N],d[N];bool vis[N];
Edge g[M];
vector<int> e[N];
inline void init(){for(int i=0;i<=n;i++) e[i].clear();n=s=t=0;ecnt=1;}
inline void add(int u,int v,int c,int w){
g[++ecnt]=(Edge){v,c,w};e[u].pb(ecnt);
g[++ecnt]=(Edge){u,0,-w};e[v].pb(ecnt);
}
queue<int> q;
inline bool spfa(){
for(int i=0;i<=n;i++) d[i]=-Inf;d[t]=-Inf;
vis[s]=1;d[s]=0;q.push(s);
while(!q.empty()){
int u=q.front();q.pop();
vis[u]=0;
for(int x:e[u]){
Edge eg=g[x];
if(eg.c<=0||d[eg.v]>=d[u]+eg.w) continue;
d[eg.v]=d[u]+eg.w;
if(!vis[eg.v]) q.push(eg.v),vis[eg.v]=1;
}
}
return d[t]!=-Inf;
}
int dfs(int u,int a){
if(u==t||!a) return a;
vis[u]=1;
int flow=0,f;
for(int &i=cur[u];i<(int)e[u].size();i++){
Edge eg=g[e[u][i]];
if(eg.c<=0||d[eg.v]!=d[u]+eg.w||vis[eg.v]) continue;
if((f=dfs(eg.v,min(eg.c,a)))){
a-=f;flow+=f;g[e[u][i]].c-=f;g[e[u][i]^1].c+=f;
if(!a) break;
}
}
vis[u]=0;
return flow;
}
int solve(){
int res=0;
while(spfa()){
for(int i=0;i<=n;i++) cur[i]=0;
res+=dfs(s,Inf)*d[t];
}return res;
}
}
vector<pii> G[N];
vector<int> edge[N];
int n,m,in[N],out[N],deg[N],dis[N];bool vis[N];
queue<int> q;
void solve(){
mcmf::init();
for(int i=1;i<=n;i++) in[i]=0,out[i]=0,deg[i]=0,edge[i].clear(),G[i].clear();
cin>>n>>m;mcmf::n=n+1;mcmf::s=0;mcmf::t=n+1;
for(int i=1,u,v;i<=m;i++){
cin>>u>>v;
mcmf::add(u,v,Inf,1);
edge[u].pb(v);
in[v]++;out[u]++;deg[v]++;
}
for(int i=1;i<=n;i++) if(!deg[i]) q.push(i);
int tot=0;
while(!q.empty()){
tot++;
int u=q.front();q.pop();
for(int v:edge[u]){
if(!--deg[v]) q.push(v);
}
}
if(tot!=n) return cout<<"NIE\n",void();
for(int i=1;i<=n;i++){
if(in[i]<out[i]) mcmf::add(mcmf::s,i,out[i]-in[i],0);
if(in[i]>out[i]) mcmf::add(i,mcmf::t,in[i]-out[i],0);
}
int res=mcmf::solve();
for(int v=1;v<=n;v++){
for(int x: mcmf::e[v]){
Edge eg=mcmf::g[x];int u=eg.v,w=eg.w;
if(w<0&&u<=n){
if(eg.c) G[u].pb(MP(v,1));
G[v].pb(MP(u,-1));
}
}
}
for(int i=1;i<=n;i++) dis[i]=1,vis[i]=1,q.push(i);
while(!q.empty()){
int u=q.front();q.pop();
vis[u]=0;
for(pii x:G[u]){
if(dis[x.v]>dis[u]+x.w){
dis[x.v]=dis[u]+x.w;
if(!vis[x.v]) q.push(x.v),vis[x.v]=1;
}
}
}
int mn=Inf;
for(int i=1;i<=n;i++) mn=min(mn,dis[i]);
for(int i=1;i<=n;i++) dis[i]-=mn-1;
cout<<res<<'\n';
for(int i=1;i<=n;i++) cout<<dis[i]<<' ';cout<<'\n';
}
signed main(){
ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
int T;cin>>T;
while(T--) solve();
return 0;
}