网络流必刷题

未完待续……
serve as网络流做题记录集&总结

注意事项: 1. 建边时忽略边权 =0 的边

【P4313】文理分科(建图技巧)

每个人选文科或理科可以有满意值,几个人同时选文科或理科也可以获得满意值,求满意值的最大值。

套路

  1. 先把所有权值加起来,源点和汇点分别代表文科和理科
  2. “文理分科”式建图:“若没割一条\((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

可能思路

  1. 转化为一个代价决策问题
    即,考虑先把所有房子都建到h,然后再来决策是交罚金还是拆楼
  2. 有可能这类决策问题多采用↓的方法
  • 凭空建立一条边(u,v)=+∞(本文中除非特殊说明(u,v)都从u到v)
  • 选择元素i的代价为从源点到i建立一条边的权值

本题解法

那么首先是基于一个贪心思想:如果我们要交罚款,那肯定要建满房子。
于是就可以

  1. 对于每个房子,建立1~h这些点,表示一开始是建了这么高的
  2. 对每个决策建一个点,把[l,r]这一段的房子的[x+1~h]的点全部向这个点连权值为+∞的边
  3. 让这个点跟汇点连条权值为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\) 表示选一个点。转化为求最小割。

    1. 正收益 \(c_x>0\)\((S,x,c_x),(x,T,0)\)
    2. 负收益 \(c_x<0\)\((S,x,0),(x,T,-c_x)\)
    3. \(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';
}
posted @ 2022-01-10 20:54  pengyule  阅读(50)  评论(0)    收藏  举报