20230812-网络流 I
20230812
最小路径覆盖
考虑把每一个点拆成两个点:入点和出点
对于每一条边
再把
这样跑一遍最大流
P2764 最小路径覆盖问题
题目描述
传送门
给定一张DAG,要求用最少的路径覆盖整张图。数据范围:
Solution
经典问题
考虑把每一个点拆成两个点:入点和出点
对于每一条边
再把
这样跑一遍最大流
H_W_Y-Coding
#include <bits/stdc++.h>
using namespace std;
const int N=6e3+5,inf=0x3f3f3f3f;
int n,m,head[N],tot=0,ans=0,lv[N],st,ed,cur[N],fa[N],cnt=0,vis[N];
struct edge{
int u,v,nxt,val;
}e[N<<1];
int read(){
int x=0,f=1;char ch=getchar();
while(!isdigit(ch)){if(ch=='-') f=-1;ch=getchar();}
while(isdigit(ch)){x=(x<<1)+(x<<3)+ch-'0';ch=getchar();}
return x*f;
}
void add(int u,int v){
e[++tot]=(edge){u,v,head[u],1};
head[u]=tot;
e[++tot]=(edge){v,u,head[v],0};
head[v]=tot;
}
bool bfs(){
for(int i=0;i<=n*2+1;i++) lv[i]=-1;
queue<int> q;
q.push(st);
lv[st]=1;
cur[st]=head[st];
while(!q.empty()){
int u=q.front();q.pop();
for(int i=head[u];i!=-1;i=e[i].nxt){
int v=e[i].v,val=e[i].val;
if(val>0&&lv[v]==-1){
q.push(v);;
cur[v]=head[v];
lv[v]=lv[u]+1;
}
}
}
return lv[ed]!=-1;
}
int dfs(int u,int flow){
if(u==ed) return flow;
int res=flow;
for(int i=cur[u];i!=-1;i=e[i].nxt){
cur[u]=i;
int v=e[i].v,val=e[i].val;
if(lv[v]==lv[u]+1&&val>0){
int c=dfs(v,min(val,res));
res-=c;
e[i].val-=c;
e[i^1].val+=c;
}
}
return flow-res;
}
int dinic(){
int res=0;
while(bfs()) res+=dfs(st,inf);
return res;
}
int find(int x){
if(fa[x]==x) return x;
return fa[x]=find(fa[x]);
}
void marge(int u,int v){
u=find(u);v=find(v);
if(u!=v) fa[u]=v;
}
vector<vector<int> > g(N);
void work(){
for(int i=1;i<=n;i++) fa[i]=i;
for(int i=0,u,v;i<=tot;i+=2){
if(e[i].val==1||e[i].u==st||e[i].v==ed) continue;
u=e[i].u;v=e[i].v;
if(u>n) u-=n;
if(v>n) v-=n;
marge(u,v);
}
cnt=0;
for(int i=1;i<=n;i++){
if(!vis[find(i)]) vis[fa[i]]=++cnt;
g[vis[fa[i]]].push_back(i);
}
for(int i=1;i<=cnt;i++){
for(int j=0;j<g[i].size();j++)
printf("%d ",g[i][j]);
printf("\n");
}
}
int main(){
/*2023.8.19 H_W_Y P2764 最小路径覆盖问题 网络最大流*/
n=read();m=read();
st=0;ed=n*2+1;
memset(head,-1,sizeof(head));tot=-1;
for(int i=1;i<=n;i++) add(st,i),add(i+n,ed);
for(int i=1,u,v;i<=m;i++){
u=read();v=read();
add(u,v+n);
}
ans=n-dinic();
work();
printf("%d\n",ans);
return 0;
}
P3254 圆桌问题
题目描述
传送门
有来自
第
为了使代表们充分交流,希望从同一个单位来的代表不在同一个餐桌就餐。
请给出一个满足要求的代表就餐方案。
Solution
简单易懂
建立一个二分图,左部点是单位,右部点是餐桌。
从
从每个单位向每个餐桌连一条容量为
从每个餐桌向
H_W_Y-Coding
#include <bits/stdc++.h>
using namespace std;
const int maxn=1e3+10,INF=0x3f3f3f3f;
int n,m,a[maxn],head[maxn],tot=-1,cur[maxn],lv[maxn],s,t,sum=0;
struct edge{
int u,v,nxt,val;
}e[maxn*maxn];
void add(int u,int v,int val){
e[++tot]=(edge){u,v,head[u],val};
head[u]=tot;
e[++tot]=(edge){v,u,head[v],0};
head[v]=tot;
}
bool bfs(){
memset(lv,-1,sizeof(lv));
queue<int> q;
q.push(s);
cur[s]=head[s];
lv[s]=1;
while(!q.empty()){
int now=q.front();q.pop();
for(int i=head[now];i!=-1;i=e[i].nxt){
int v=e[i].v,val=e[i].val;
if(lv[v]==-1&&val>0){
q.push(v);
cur[v]=head[v];
lv[v]=lv[now]+1;
}
}
}
return lv[t]!=-1;
}
int dfs(int now,int flow){
if(now==t) return flow;
int res=flow;
for(int i=cur[now];i!=-1;i=e[i].nxt){
cur[now]=i;
int v=e[i].v,val=e[i].val;
if(val>0&&lv[v]==lv[now]+1){
int c=dfs(v,min(res,val));
res-=c;
e[i].val-=c;
e[i^1].val+=c;
}
}
return flow-res;
}
int dinic(){
int ans=0;
while(bfs()) ans+=dfs(0,INF);
return ans;
}
int main(){
/*2023.2.25 hewanying P3254 [网络流24题]圆桌问题 网络最大流*/
scanf("%d%d",&m,&n);
s=0,t=n+m+2;
memset(head,-1,sizeof(head));
for(int i=1;i<=m;i++){
scanf("%d",&a[i]);
add(s,i,a[i]);
sum+=a[i];
}
for(int i=m+1;i<=n+m;i++){
scanf("%d",&a[i]);
add(i,t,a[i]);
for(int j=1;j<=m;j++) add(j,i,1);
}
if(dinic()==sum){
printf("1\n");
for(int i=1;i<=m;i++){
for(int j=head[i];j!=-1;j=e[j].nxt)
if(!(j&1)&&e[j].val==0) printf("%d ",e[j].v-m);
printf("\n");
}
}
else printf("0\n");
return 0;
}
P2472 [SCOI2007] 蜥蜴
题目描述
传送门
一个
有一些石柱的顶上有蜥蜴,蜥蜴每次可以跳到距离不超过
蜥蜴跳一次, 它所在的石柱的高度就减一,如果某个石柱的高度为
Solution
简单易懂
将每个石柱拆成两个点,之间连容量为高度
H_W_Y-Coding
#include <bits/stdc++.h>
using namespace std;
const int maxn=1e3+10,INF=0x3f3f3f3f;
int n,m,d,lv[maxn],s,t,head[maxn],tot=-1,x,cur[maxn],a[maxn][maxn],sum=0;
string st;
struct edge{
int u,v,nxt,val;
}e[maxn*maxn];
void add(int u,int v,int val){
e[++tot]=(edge){u,v,head[u],val};
head[u]=tot;
e[++tot]=(edge){v,u,head[v],0};
head[v]=tot;
}
bool bfs(){
memset(lv,-1,sizeof(lv));
lv[s]=0;cur[s]=head[s];
queue<int> q;
q.push(s);
while(!q.empty()){
int now=q.front();q.pop();
for(int i=head[now];i!=-1;i=e[i].nxt){
int v=e[i].v,val=e[i].val;
if(val>0&&lv[v]==-1){
lv[v]=lv[now]+1;
cur[v]=head[v];
q.push(v);
}
}
}
return lv[t]!=-1;
}
int dfs(int now,int flow){
if(now==t) return flow;
int res=flow;
for(int i=cur[now];i!=-1;i=e[i].nxt) {
cur[now]=i;
int v=e[i].v,val=e[i].val;
if(val>0&&lv[v]==lv[now]+1){
int c=dfs(v,min(res,val));
res-=c;
e[i].val-=c;
e[i^1].val+=c;
}
}
return flow-res;
}
int dinic(){
int ans=0;
while(bfs()) ans+=dfs(s,INF);
return ans;
}
int main(){
/*2023.3.4 hewanying P2472 蜥蜴 网络最大流*/
memset(head,-1,sizeof(head));tot=-1;
scanf("%d%d%d",&n,&m,&d);
s=0;t=2*n*m+1;
for(int i=1;i<=n;i++){
cin>>st;
for(int j=1;j<=m;j++){
a[i][j]=x=st[j-1]-'0';
if(x==0) continue;
add(2*(n*(i-1)+j)-1,2*(n*(i-1)+j),x);
if(i<=d||n-i<d||j<=d||m-j<d) add(2*(n*(i-1)+j),t,INF);
for(int k=1;k<=i;k++)
for(int k2=1;k2<=m;k2++){
if(k==i&&k2==j) break;
if((k-i)*(k-i)+(j-k2)*(j-k2)<=d*d&&a[k][k2]>0){
add(2*(n*(k-1)+k2),2*(n*(i-1)+j)-1,INF);
add(2*(n*(i-1)+j),2*(n*(k-1)+k2)-1,INF);
}
}
}
}
for(int i=1;i<=n;i++){
cin>>st;
for(int j=0;j<m;j++)
if(st[j]=='L') sum++,add(s,2*(n*(i-1)+j+1)-1,1);
}
printf("%d\n",sum-dinic());
return 0;
}
P2765 魔术球问题
题目描述
传送门
假设有
- 每次只能在某根柱子的最上面放球。
- 在同一根柱子中,任何
个相邻球的编号之和为完全平方数。
试设计一个算法,计算出在 根柱子上最多能放多少个球。
。
Solution
发现如果对于前面的
那么我们可以把这
在这个图上跑出最小路径覆盖
如果最小路径覆盖
这
这样我们考虑去枚举
每一次多一个数就加上与它相关的边
再在残图上面跑网络流即可
所以时间复杂度是可以保证的
H_W_Y-Coding
#include <bits/stdc++.h>
using namespace std;
const int N=2e4+5,k=1e4,inf=0x3f3f3f3f;
int n,m,head[N],tot=-1,cur[N],lv[N],st,ed,ans,sum,num,sq[N];
struct edge{
int u,v,nxt,val;
}e[N<<2];
void add(int u,int v){
e[++tot]=(edge){u,v,head[u],1};
head[u]=tot;
e[++tot]=(edge){v,u,head[v],0};
head[v]=tot;
}
bool bfs(){
memset(lv,-1,sizeof(lv));
lv[st]=1;
queue<int> q;
q.push(st);
cur[st]=head[st];
while(!q.empty()){
int u=q.front();q.pop();
for(int i=head[u];i!=-1;i=e[i].nxt){
int v=e[i].v,val=e[i].val;
if(val>0&&lv[v]==-1){
lv[v]=lv[u]+1;
q.push(v);
cur[v]=head[v];
}
}
}
return lv[ed]!=-1;
}
int dfs(int u,int flow){
if(u==ed) return flow;
int res=flow;
for(int i=cur[u];i!=-1;i=e[i].nxt){
cur[u]=i;
int v=e[i].v,val=e[i].val;
if(val>0&&lv[v]==lv[u]+1){
int c=dfs(v,min(res,val));
e[i].val-=c;
res-=c;
e[i^1].val+=c;
}
}
return flow-res;
}
int dinic(){
int res=0;
while(bfs()) res+=dfs(st,inf);
return res;
}
int fa[N],vis[N];
int find(int x){return (fa[x]==x)?x:(fa[x]=find(fa[x]));}
void marge(int u,int v){
u=find(u);v=find(v);
if(u!=v) fa[u]=v;
}
vector<vector<int> >g(N);
void work(){
for(int i=1;i<=m;i++) fa[i]=i;
for(int i=0;i<=tot;i+=2){
if(e[i].u==st||e[i].v==ed||e[i].val==1) continue;
int u=((e[i].u>k)?(e[i].u-k):e[i].u),v=((e[i].v>k)?(e[i].v-k):e[i].v);
marge(u,v);
}
int cnt=0;
for(int i=1;i<=m;i++){
if(!vis[find(i)]) vis[fa[i]]=++cnt;
g[vis[fa[i]]].push_back(i);
}
for(int i=1;i<=cnt;i++){
for(int j=0;j<g[i].size();j++)
printf("%d ",g[i][j]);
printf("\n");
}
}
int main(){
/*2023.8.19 H_W_Y P2765 魔术球问题 网络最大流*/
scanf("%d",&n);
st=0;ed=20001;m=1;ans=0,sum=0;num=1;
memset(head,-1,sizeof(head));tot=-1;
for(int i=1;i<=k;i++) sq[i]=i*i;
while(1){
add(st,m);add(m+k,ed);
int c=upper_bound(sq+1,sq+k+1,m)-sq;
for(int i=c;sq[i]-m<m;i++) add(sq[i]-m,m+k);
ans=dinic();sum+=ans;
if(m-sum>n) break;
m++;
}
m--;
printf("%d\n",m);
work();
return 0;
}
最大点独立集
最大点独立集,顾名思义,要求选出一个点集是每两个点之间都不相连
在二分图中,最大点独立集=
很显然可以感受到,简单易懂
CF1404E Bricks
题目描述
你有一个
Solution
想了半天没有想通
考虑对于每一条连接两个黑色方块的边,
我们把这两个方块合并就意味着把这一条边擦除
那么我们一定是要让擦除的边越多越好了
但是发现边的选择是有条件的
我们不能同时选择一个黑色方块的横边和竖边
发现这是很显然的一个二分图了
我们把不能一起选的横边和竖边连在一起
那么我们希望擦除的边就是最大点独立集
再用网络流跑二分图最大匹配即可
最后的答案就是
H_W_Y-Coding
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+5,inf=0x3f3f3f3f;
int a[205][205],n,m,head[N],cnt=0,tot=-1,lv[N],st,ed,cur[N],k,id,tmp,sum=0;
bool flag=false,vis[N];
struct edge{
int v,nxt,val;
}e[N<<5];
void add(int u,int v){
e[++tot]=(edge){v,head[u],1};
head[u]=tot;
e[++tot]=(edge){u,head[v],0};
head[v]=tot;
}
bool bfs(){
memset(lv,-1,sizeof(lv));
queue<int > q;
q.push(st);
lv[st]=1;cur[st]=head[st];
while(!q.empty()){
int u=q.front();q.pop();
for(int i=head[u];i!=-1;i=e[i].nxt){
int v=e[i].v,val=e[i].val;
if(val>0&&lv[v]==-1){
lv[v]=lv[u]+1;
cur[v]=head[v];
q.push(v);
}
}
}
return lv[ed]!=-1;
}
int dfs(int u,int flow){
if(u==ed) return flow;
int res=flow;
for(int i=cur[u];i!=-1;i=e[i].nxt){
cur[u]=i;
int v=e[i].v,val=e[i].val;
if(val>0&&lv[v]==lv[u]+1){
int c=dfs(v,min(res,val));
res-=c;
e[i].val-=c;
e[i^1].val+=c;
}
}
return flow-res;
}
int dinic(){
int res=0;
while(bfs()) res+=dfs(st,inf);
return res;
}
int main(){
/*2023.8.20 H_W_Y CF1404E Bricks 最大独立点集*/
memset(head,-1,sizeof(head));tot=-1;cnt=0;
scanf("%d%d",&n,&m);
k=m*(n-1);
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++){
char ch=getchar();
while(ch!='#'&&ch!='.') ch=getchar();
if(ch=='#') a[i][j]=1,sum++;
}
st=0;ed=2*n*m+1;
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++){
if(!a[i][j]) continue;
if(a[i][j-1]){
id=k+(i-1)*(m-1)+j-1;
vis[id]=true;
add(id,ed);
}
if(a[i-1][j]){
id=(i-2)*m+j;flag=false;
add(st,id);vis[id]=true;
if(a[i][j-1]){
tmp=k+(i-1)*(m-1)+j-1;
vis[tmp]=true;
add(id,tmp);
}
if(a[i][j+1]){
tmp=k+(i-1)*(m-1)+j;
vis[tmp]=true;
add(id,tmp);
}
}
if(a[i+1][j]){
id=(i-1)*m+j;flag=false;
if(a[i][j-1]){
tmp=k+(i-1)*(m-1)+j-1;
vis[tmp]=true;
add(id,tmp);flag=true;
}
if(a[i][j+1]){
tmp=k+(i-1)*(m-1)+j;
vis[tmp]=true;
add(id,tmp);flag=true;
}
vis[id]|=flag;
}
}
for(int i=1;i<=2*n*m;i++)
if(vis[i])
cnt++;
printf("%d\n",sum-(cnt-dinic()));
return 0;
}
最小割最大流问题
最小割:把图划分成两个子集,所需要割掉的最小容量
感性理解:最小割=最大流
如何求最小割的子集?
从
能走到的点就是最小割
最大权闭合子图
给定一个有向图,点有点权。
如果一个点
求最大收益。(点权可以为负数)
利用最小割来解决。先假设所有正点权都选。
正点权连到
原图中所有
P2762 太空飞行计划问题
题目描述
有
Solution
简单题
差不多是最大权闭合子图问题的模板
H_W_Y-Coding
#include <bits/stdc++.h>
using namespace std;
const int N=1e3+5,inf=0x3f3f3f3f;
int n,m,a[N],head[N],tot=-1,cur[N],lv[N],sum=0,ans=0,st,ed;
bool flag=true;
struct edge{
int v,nxt,val;
}e[N<<4];
bool read(int &x){
x=0;
int f=1;char ch=getchar();
if(ch=='\n') return ch=0,-1;
while(!isdigit(ch)){if(ch=='-') f=-1;ch=getchar();}
while(isdigit(ch)){x=(x<<1)+(x<<3)+ch-'0';ch=getchar();}
x*=f;
return ((ch!='\r')&&(ch!='\n'));
}
void add(int u,int v,int w){
e[++tot]=(edge){v,head[u],w};
head[u]=tot;
e[++tot]=(edge){u,head[v],0};
head[v]=tot;
}
bool bfs(){
memset(lv,-1,sizeof(lv));
queue<int> q;
lv[st]=1;
cur[st]=head[st];
q.push(st);
while(!q.empty()){
int u=q.front();q.pop();
for(int i=head[u];i!=-1;i=e[i].nxt){
int v=e[i].v,val=e[i].val;
if(val>0&&lv[v]==-1){
lv[v]=lv[u]+1;
cur[v]=head[v];
q.push(v);
}
}
}
return lv[ed]!=-1;
}
int dfs(int u,int flow){
if(u==ed) return flow;
int res=flow;
for(int i=cur[u];i!=-1;i=e[i].nxt){
cur[u]=i;
int v=e[i].v,val=e[i].val;
if(val>0&&lv[v]==lv[u]+1){
int c=dfs(v,min(res,val));
res-=c;
e[i].val-=c;
e[i^1].val+=c;
}
}
return flow-res;
}
int dinic(){
int res=0;
while(bfs()) res+=dfs(st,inf);
return res;
}
int main(){
/*2023.8.20 H_W_Y P2762 太空飞行计划问题 最小割最大流+最大权闭合子图*/
memset(head,-1,sizeof(head));tot=-1;
read(m);read(n);
st=0,ed=n+m+1;
for(int i=1,x;i<=m;i++){
read(a[i]);add(st,i,a[i]);sum+=a[i];
flag=true;
do{
flag=read(x);
add(i,x+m,inf);
}while(flag);
}
for(int i=1;i<=n;i++) read(a[i+m]),add(i+m,ed,a[i+m]);
ans=dinic();
for(int i=1;i<=m;i++) if(lv[i]!=-1) printf("%d ",i);
printf("\n");
for(int i=1;i<=n;i++) if(lv[i+m]!=-1) printf("%d ",i);
printf("\n%d\n",sum-ans);
return 0;
}
P4177 [CEOI2008] order
题目描述
传送门
有
Solution
同样是最大权最小子图,只不过多了一个租用的操作
租用也是比较好处理的
我们直接把中间的边的容量
网络流一定要剪枝!!!
H_W_Y-coding
#include <bits/stdc++.h>
using namespace std;
const int N=3e6+5,inf=0x3f3f3f3f;
int n,m,head[N],st,ed,tot=-1,sum=0,cur[N],lv[N],l,r,q[N];
struct edge{
int v,nxt,val;
}e[N<<2];
int read(){
int x=0,f=1;char ch=getchar();
while(!isdigit(ch)){if(ch=='-') f=-1;ch=getchar();}
while(isdigit(ch)){x=(x<<1)+(x<<3)+ch-'0';ch=getchar();}
return x*f;
}
void add(int u,int v,int w){
e[++tot]=(edge){v,head[u],w};
head[u]=tot;
e[++tot]=(edge){u,head[v],0};
head[v]=tot;
}
bool bfs(){
for(int i=0;i<=ed;i++) lv[i]=-1;
lv[st]=1;
cur[st]=head[st];
q[l=1]=st;r=1;
while(l<=r){
int u=q[l++];
for(int i=head[u];i!=-1;i=e[i].nxt){
int v=e[i].v,val=e[i].val;
if(val>0&&lv[v]==-1){
lv[v]=lv[u]+1;
cur[v]=head[v];
q[++r]=v;
if(v==ed) return true;
}
}
}
return lv[ed]!=-1;
}
int dfs(int u,int flow){
if(u==ed) return flow;
int res=flow;
for(int i=cur[u];i!=-1;i=e[i].nxt){
cur[u]=i;
int v=e[i].v,val=e[i].val;
if(val>0&&lv[v]==lv[u]+1){
int c=dfs(v,min(res,val));
if(!c) lv[v]=-1;
res-=c;
e[i].val-=c;
e[i^1].val+=c;
}
if(res==0) break;//网络流剪枝很有必要!!!
}
if(res!=0) lv[u]=-1;
return flow-res;
}
int dinic(){
int res=0;
while(bfs()) res+=dfs(st,inf);
return res;
}
int main(){
/*2023.8.23 H_W_Y P4177 [CEOI2008] order 网络流+最大权闭合子图*/
memset(head,-1,sizeof(head));tot=-1;
n=read();m=read();
st=0,ed=n+m+1;
for(int i=1,x,t;i<=n;i++){
x=read();add(st,i,x);sum+=x;
t=read();
for(int j=1,a,b;j<=t;j++){
a=read();b=read();
add(i,a+n,b);
}
}
for(int i=1,x;i<=m;i++){x=read();add(i+n,ed,x);}
printf("%d\n",sum-dinic());
return 0;
}
本文作者:H_W_Y
本文链接:https://www.cnblogs.com/H-W-Y/p/17642433.html
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步