网络流必刷题
未完待续……
serve as网络流做题记录集&总结
注意事项: 1. 建边时忽略边权 =0 的边
【P4313】文理分科(建图技巧)
每个人选文科或理科可以有满意值,几个人同时选文科或理科也可以获得满意值,求满意值的最大值。
套路
- 先把所有权值加起来,源点和汇点分别代表文科和理科
- “文理分科”式建图:“若没割一条\((S-u)(u\in [l,r])\)的边,则必须割一个额外的权值”(不影响其他的边的割法)——\([c_i](S-1)(S-2)(S-3)...(S,n)+[c'_i(1,T)(2,T)(3,T)...(n,T)+[inf](l,V)(l+1,V)...(r,V)+[a_{[l,r]}](V,T)\)
【CF1146G】Zoning Restrictions(建图技巧+贪心+最大流)
https://www.luogu.com.cn/problem/CF1146G
可能思路
- 转化为一个代价决策问题
即,考虑先把所有房子都建到h,然后再来决策是交罚金还是拆楼 - 有可能这类决策问题多采用↓的方法
- 凭空建立一条边(u,v)=+∞(本文中除非特殊说明(u,v)都从u到v)
- 选择元素i的代价为从源点到i建立一条边的权值
本题解法
那么首先是基于一个贪心思想:如果我们要交罚款,那肯定要建满房子。
于是就可以
- 对于每个房子,建立1~h这些点,表示一开始是建了这么高的
- 对每个决策建一个点,把[l,r]这一段的房子的[x+1~h]的点全部向这个点连权值为+∞的边
- 让这个点跟汇点连条权值为c的边
图解:(不要看白色线,画错了)
所以答案就是h*h*n-最小割。
点击查看代码
#include <bits/stdc++.h>
using namespace std;
const int N=2555,M=260000; //边要数清楚
int n,h,m,tot=1,dis[N],we[M];
vector<pair<int,int> >G[N];
queue<int>Q;
bool bfs(){
memset(dis,0,sizeof(dis));
Q.push(1),dis[1]=1;
while(!Q.empty()){
int x=Q.front();Q.pop();
for(int i=0;i<G[x].size();i++){
int y=G[x][i].first,z=G[x][i].second;
if(we[z]&&!dis[y])dis[y]=dis[x]+1,Q.push(y);
}
}
return dis[2];
}
int dfs(int x,int in){
if(x==2)return in;
int out=0;
for(int i=0;i<G[x].size();i++){
int y=G[x][i].first,z=G[x][i].second;
if(we[z]&&dis[y]==dis[x]+1){
int los=dfs(y,min(in,we[z]));
we[z]-=los,we[z^1]+=los,in-=los,out+=los;
}
}
if(!out)dis[x]=-1;
return out;
}
void ade(int u,int v,int w){
G[u].push_back(make_pair(v,++tot)),we[tot]=w;
G[v].push_back(make_pair(u,++tot)),we[tot]=0;
}
int main(){
cin>>n>>h>>m;
for(int i=1;i<=n;i++)for(int j=1;j<=h;j++)ade(1,2+(i-1)*h+j,2*j-1);
for(int i=1,l,r,x,c;i<=m;i++){
cin>>l>>r>>x>>c;
ade(2+n*h+i,2,c);
for(int j=l;j<=r;j++)for(int k=x+1;k<=h;k++)ade(2+(j-1)*h+k,2+n*h+i,1e9);
}
int ans=0;
while(bfs())ans+=dfs(1,1e9);
cout<<h*h*n-ans;
}
【SCOI2015】小凸玩矩阵(二分答案+建二分图+最大流)
一个 \(n\times m\) 矩阵 \(a\),从中选 \(k\) 个元素,要求不能有两个元素在同一行或同一列,最小值最大是多少?\(n,m\le 250,a_{i,j}\le 10^9\)
可能思路
- 最小值最大:二分一个最小值,看能不能选到 \(\ge k\) 个 \(\ge mid\) 的元素
- 试图选尽量多的 \(\ge mid\) 的元素,那么 \(\ge mid\) 的元素有贡献,而 \(<mid\) 的元素没贡献
- 考虑如何限制每行每列都只能有一个元素:用二分图来表示行和列,\(i_{X},j_{Y}\) 之间连边权 \([a_{i,j}\ge mid]\) 的边
- \(S\to 1_X,2_X,...,n_X;1_Y,2_Y,...,m_Y\to T\),边权都是 \(1\),然后跑最大流
【六省联考2017】寿司餐厅(最大权闭合子图)
有一排 \(n\) 个寿司 \(a_1,a_2,...,a_n\),你可以选择任意多个不同的区间 \([l_i,r_i]\),区间 \([l,r]\) 的收益是 \(d_{l,r}\);一旦选了 \([l,r]\) 就要选它的所有子区间;
如果选过的区间的并含有 \(c\) 个寿司 \(x\),则产生 \(mx^2+cx\) 的负收益(\(m\) 为给定的常数)。
问净收益的最大值。\(n\le 100,a_i\le 1000,-500\le d_{i,j}\le 500\)
可能思路
- 观察性质:“选 A 就得选 B”————>最大权闭合子图经典模型
最大权闭合子图:一种网络流模型,适用于「有若干个物品,选每个物品可以获得正收益或负收益,若干条“选 A 就得选 B”的限制」的问题。
连边方式:\(S\to x\) 表示不选一个点,\(x\to T\) 表示选一个点。转化为求最小割。- 正收益 \(c_x>0\):\((S,x,c_x),(x,T,0)\)
- 负收益 \(c_x<0\):\((S,x,0),(x,T,-c_x)\)
- 选 \(x\) 就得选 \(y\):\((x,y,+\infty)\);这样一旦选了 \(x\),也就是割掉 \(x\to T\),假若不选 \(y\),也就是割掉 \(S\to y\),就会留下路径 \(S\to x\to y\to T\),没有割成功,所以必须割 \(y\to T\)。
- 收益的拆解,分到每个人头上:观察 \(mx^2+cx\) 的结构,\(cx\) 看成选一个 \(x\) 产生 \(x\) 负收益,\(mx^2\) 看成选到“类 \(x\)”产生 \(mx^2\) 负收益,那么对于每一个 \(a_i\),都存在选了“寿司 \(a_i\)”就必须选“类 \(a_i\)”的限制条件,还是可以用最大权闭合子图。
【TJOI2015】线性代数(最大权闭合子图)
给出 \(B_{n\times n},C_{1\times n}\),你来设定 01 矩阵 \(A_{1\times n}\),求 \(D=(A\times B-C)\times A^{\rm T}\) 的最大值。\(n\le 500,B_{i,j},C_i\le 1000\)
可能思路
- 不要看错题,\(A\) 是 01 矩阵
- 本题难度较低,经过化简:\(A\times B\times A^{\rm T}=\sum_{i,j}A_iA_jB_{i,j};C\times A^{\rm T}=\sum C_iA_i\) 就容易想到用网络流来解决
- 建边:显然存在“选了 \(A_i\) 且 \(a_j\) 就必须选 \(B_{i,j}\)”“选了 \(A_i\) 就必须选 \(C_i\)”这两条限制,后者最大权闭合子图显然,前者需要转化成“不选 \(A_i\) 就不选 \(B_{i,j}\)”的关系,同样最大权闭合子图显然。
【HDU6431】Always Online(位运算+最小割)
给定一无向网络,满足 \(\forall u\ne v\),\(u\) 到 \(v\) 的简单路径不超过 2 条。
求 \(\sum_{1\le s<t\le n}s\operatorname{xor}t\operatorname{xor}\operatorname{maxflow}(s,t)\)。\(n\le 10^5,边的容量\le 10^9\)
可能思路
- \(10^9\times (10^5)^2=10^{19}\),要开 ULL
- 由于任意 \(u\) 到 \(v\) 的简单路径不超过 2 条,因此图中只存在单环和树边,由最大流=最小割,而 \(s\) 到 \(t\) 的最小割要么割掉一条树边,要么割掉一条环上的两条边,而进一步考察容易发现后者的两条边之一必然是环上的最小边,所以可以把每个环的一条最小边加给环上其它边,原图变成一棵树,\(s\) 到 \(t\) 的最小割转为树上路径上最小边
- 根据经典算法,从大到小加入边并使用并查集维护连通块信息;由于贡献形如按位异或,需要存储按位信息,即每个连通块中二进制第 \(i\) 位 \(=0/1\) 的点的个数
#include <bits/stdc++.h>
#define int unsigned long long
#define pii pair<int,int>
#define fi first
#define se second
using namespace std;
inline int read(){
register char ch=getchar();register int x=0;
while(ch<'0'||ch>'9')ch=getchar();
while(ch>='0'&&ch<='9')x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
return x;
}
const int N=1.5e5+5;
int n,m,fa[N],f[N][17][2],dep[N],anc[N][18],cf[N];
vector<pii>G[N];
struct E {int u,v,w,sel;}e[N];
int find(int x){return x==fa[x]?x:fa[x]=find(fa[x]);}
void dfs1(int x,int p){
dep[x]=dep[p]+1,anc[x][0]=p;
for(int i=1;i<=17;i++)anc[x][i]=anc[anc[x][i-1]][i-1];
for(pii y:G[x])if(y.fi^p)dfs1(y.fi,x);
}
void dfs2(int x,int p){
for(pii y:G[x])if(y.fi^p){
dfs2(y.fi,x);
cf[x]+=cf[y.fi];
e[y.se].w+=cf[y.fi];
}
}
int glca(int u,int v){
if(u==v)return u;
if(dep[u]>dep[v])swap(u,v);
for(int i=17;~i;i--)if(dep[anc[v][i]]>=dep[u])v=anc[v][i];
if(u==v)return u;
for(int i=17;~i;i--)if(anc[u][i]!=anc[v][i])u=anc[u][i],v=anc[v][i];
return anc[u][0];
}
void solve(){
n=read(),m=read();
for(int i=1;i<=n;i++)G[i].clear(),cf[i]=0;
for(int i=1;i<=m;i++)e[i].u=read(),e[i].v=read(),e[i].w=read();
sort(e+1,e+m+1,[](E a,E b){return a.w>b.w;});
for(int i=1;i<=n;i++)fa[i]=i;
for(int i=1;i<=m;i++){
if(find(e[i].u)!=find(e[i].v)){
G[e[i].u].push_back(make_pair(e[i].v,i));
G[e[i].v].push_back(make_pair(e[i].u,i));
fa[find(e[i].v)]=find(e[i].u);
e[i].sel=0;
}
else e[i].sel=1;
}
dfs1(1,0);
for(int i=1;i<=m;i++)if(e[i].sel){
cf[e[i].u]+=e[i].w,cf[e[i].v]+=e[i].w,cf[glca(e[i].u,e[i].v)]-=2*e[i].w;
}
dfs2(1,0);
sort(e+1,e+m+1,[](E a,E b){return a.w>b.w;});
for(int i=1;i<=n;i++){
fa[i]=i;
for(int j=0;j<17;j++)f[i][j][i>>j&1]=1,f[i][j][!(i>>j&1)]=0;
}
int ans=0;
for(int i=1;i<=m;i++)if(!e[i].sel){
int x=find(e[i].u),y=find(e[i].v);
ans+=(f[x][0][1]+f[x][0][0])*(f[y][0][1]+f[y][0][0])*((e[i].w>>17)<<17);
for(int j=0;j<17;j++){
int b=e[i].w>>j&1;
for(int o1=0;o1<=1;o1++)for(int o2=0;o2<=1;o2++)
ans+=f[x][j][o1]*f[y][j][o2]*((o1^o2^b)<<j);
for(int o=0;o<=1;o++)f[x][j][o]+=f[y][j][o];
}
fa[y]=x;
}
cout<<ans<<'\n';
}
signed main(){int T=read();while(T--)solve();}
【AGC031E】Snuke the Phantom Thief(拆点+费用流)
在二维平面上,有 \(n\) 颗珠宝,第\(i\)颗珠宝在 \((x_i,y_i)\) 的位置,价值为 \(v_i\)。现在有一个盗贼想要偷这些珠宝。
现在给出 \(m\) 个限制约束偷的珠宝,约束有以下四种:
- 横坐标小于等于 \(a_i\) 的珠宝最多偷 \(b_i\) 颗。
- 横坐标大于等于 \(a_i\) 的珠宝最多偷 \(b_i\) 颗。
- 纵坐标小于等于 \(a_i\) 的珠宝最多偷 \(b_i\) 颗。
- 纵坐标大于等于 \(a_i\) 的珠宝最多偷 \(b_i\) 颗。
现在问你在满足这些约束的条件下,盗贼偷的珠宝的最大价值和是多少。
\(1\ \leq\ N\ \leq\ 80\), \(1\ \leq\ M\ \leq\ 320\), $1\ \leq\ x_i,\ y_i\ \leq\ 100 $, \(1\ \leq\ a_i\ \leq\ 100\), \(0\ \leq\ b_i\ \leq\ N\ -\ 1\),\((x_i,\ y_i)\) 互异。
可能思路
-
尝试去枚举偷多少个珠宝
-
考虑翻译限制:
- 横坐标排名 \(\ge b_i+1\) 的珠宝的横坐标 \(\ge a_i+1\)
- 横坐标排名 \(\le k-b_i\) 的珠宝的横坐标 \(\le a_i-1\)
- 纵坐标排名 \(\ge b_i+1\) 的珠宝的纵坐标 \(\ge a_i+1\)
- 纵坐标排名 \(\le k-b_i\) 的珠宝的纵坐标 \(\le a_i-1\)
于是知道了横坐标排名第 \(i\) 的珠宝的横坐标的范围,和纵坐标~。
-
二维问题+网络流----->拆点成横纵坐标+其间连边 \((1,v)\)
-
建 \(k+k\) 个点分别表示横坐标、纵坐标排名第 \(1\le i\le k\) 的点,代表横坐标排名第 \(i\) 的点 向 拆点的横坐标代表的点 当中处在 \([lx_i,rx_i]\) 内的点连一条从前者到后者边,代表纵坐标排名第 \(i\) 的点 向 拆点的纵坐标代表的点 当中处在 \([ly_i,ry_i]\) 内的点连一条从后者向前者的边。源点向横的 \(k\) 个点,纵的 \(k\) 个点向汇点,各连一条边。这里的所有边都是 \((1,0)\)
-
跑一遍最大费用最大流
#include <bits/stdc++.h>
#define int long long
#define pii pair<int,int>
#define fi first
#define se second
using namespace std;
inline int read(){
register char ch=getchar();register int x=0;
while(ch<'0'||ch>'9')ch=getchar();
while(ch>='0'&&ch<='9')x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
return x;
}
const int N=30000,INF=0x3f3f3f3f3f3f3f3f;
int n,m,S,T,q,tot=1,x[N],y[N],v[N],vis[N],dis[N],lx[N],rx[N],ly[N],ry[N],we[N],
idnx[N],idny[N],idkx[N],idky[N],a[N],b[N];
char ch[N][2];
struct E {int y,z,c;}fr[N];
vector<E>G[N];
queue<int>Q;
void adde(int u,int v,int w,int c){
G[u].push_back(E{v,++tot,c}),we[tot]=w;
G[v].push_back(E{u,++tot,-c}),we[tot]=0;
}
bool spfa(){
memset(dis,-0x3f,sizeof dis);
Q.push(S),dis[S]=0;
while(!Q.empty()){
int x=Q.front();Q.pop();vis[x]=0;
for(E e:G[x]){
if(we[e.z]&&dis[e.y]<dis[x]+e.c){
fr[e.y]=E{x,e.z,e.c};
dis[e.y]=dis[x]+e.c;
if(!vis[e.y])vis[e.y]=1,Q.push(e.y);
}
}
}
return dis[T]!=dis[0];
}
signed main(){
n=read();
for(int i=1;i<=n;i++)x[i]=read(),y[i]=read(),v[i]=read();
m=read();
for(int i=1;i<=m;i++)scanf("%s",ch[i]),a[i]=read(),b[i]=read();
S=1,T=2;
int ans=0;
for(int k=1;k<=n;k++){
for(int i=1;i<=q;i++)G[i].clear();
tot=1;
q=2;
for(int i=1;i<=k;i++)idkx[i]=++q;
for(int i=1;i<=n;i++)idnx[i]=++q;
for(int i=1;i<=n;i++)idny[i]=++q;
for(int i=1;i<=k;i++)idky[i]=++q;
for(int i=1;i<=k;i++)lx[i]=ly[i]=-INF,rx[i]=ry[i]=INF;
for(int i=1;i<=m;i++){
if(ch[i][0]=='L')for(int j=b[i]+1;j<=k;j++)lx[j]=max(lx[j],a[i]+1);
if(ch[i][0]=='R')for(int j=1;j<=k-b[i];j++)rx[j]=min(rx[j],a[i]-1);
if(ch[i][0]=='D')for(int j=b[i]+1;j<=k;j++)ly[j]=max(ly[j],a[i]+1);
if(ch[i][0]=='U')for(int j=1;j<=k-b[i];j++)ry[j]=min(ry[j],a[i]-1);
}
for(int i=1;i<=k;i++)for(int j=1;j<=n;j++)
if(lx[i]<=x[j]&&x[j]<=rx[i])adde(idkx[i],idnx[j],1,0);
for(int i=1;i<=k;i++)for(int j=1;j<=n;j++)
if(ly[i]<=y[j]&&y[j]<=ry[i])adde(idny[j],idky[i],1,0);
for(int i=1;i<=n;i++)adde(idnx[i],idny[i],1,v[i]);
for(int i=1;i<=k;i++)adde(S,idkx[i],1,0),adde(idky[i],T,1,0);
int Cost=0;
while(spfa()){//puts("");
int mn=INF;
for(int i=T;i!=S;i=fr[i].y)mn=min(mn,we[fr[i].z]);
for(int i=T;i!=S;i=fr[i].y)we[fr[i].z]-=mn,we[fr[i].z^1]+=mn,Cost+=mn*fr[i].c;
}
ans=max(ans,Cost);
}
cout<<ans<<'\n';
}