网络流24题:
from:Luogu

  • P4011 孤岛营救问题
  • P2756 飞行员配对方案问题
  • P2761 软件补丁问题
  • P4016 负载平衡问题
  • P3358 最长k可重区间集问题
  • P4014 分配问题
  • P4009 汽车加油行驶问题
  • P4015 运输问题
  • P2774 方格取数问题
  • P4012 深海机器人问题
  • P2762 太空飞行计划问题
  • P2770 航空路线问题
  • P2754 [CTSC1999]家园 / 星际转移问题
  • P3254 圆桌问题
  • P1251 餐巾计划问题
  • P2763 试题库问题
  • P2766 最长不下降子序列问题
  • P3355 骑士共存问题
  • P3357 最长k可重线段集问题
  • P4013 数字梯形问题
  • P3356 火星探险问题
  • P2765 魔术球问题
  • P2764 最小路径覆盖问题
  • P2775 机器人路径规划问题

0.前言

网络流24题总体来说基本难度不大(混进了一些奇怪的东西),因为网络流的模型实在太多了,像我这种无法举一反三的菜鸡只能靠多做来积累

网络流题目的核心也是难点在于怎么将问题进行模型转化从而建图,在本文中我会解释每一题的心路历程,建图方式,以及为什么要这样建图,(作为一篇网络流题解不解释建图方式等于啥也没说)

1.P4011 孤岛营救问题

首先,这不是一个网络流题目

你要问我为什么的话我只能说:老婆饼里面也没有老婆!

题意:有一些钥匙散布在一些地方,有些地方有障碍,有的地方有门,需要拥有对应的钥匙才能通过这条门,一个地方可能有多把钥匙

思路:因为要保存钥匙的状态,开数组不方便并且有空间压力,所以状压,0-1 BFS即可

状态设置为:
x,y :坐标
key:当前拥有的钥匙
dis:离原点的距离

#include<bits/stdc++.h>
using namespace std;
const int maxn=20;
const int dx[]={0,0,1,-1};
const int dy[]={1,-1,0,0};
int e[maxn][maxn][maxn][maxn],key[maxn][maxn][maxn];
int cnt[maxn][maxn],vis[maxn][maxn][1<<15];
int n,m,k,p,s;
struct node{int x,y,key,dis;
};
inline int getkey(int x,int y){int ans=0;for(int i=1;i<=cnt[x][y];i++){ans|=(1<<(key[x][y][i]-1));}return ans;
}
inline int bfs(int sx,int sy){int skey=getkey(sx,sy);vis[sx][sy][skey]=1;queue<node>q;q.push((node){sx,sy,skey,0});while(!q.empty()){node u=q.front();q.pop();if(u.x==n&&u.y==m){return u.dis;}int nowkey=u.key;for(int i=0;i<4;i++){int xx=u.x+dx[i];int yy=u.y+dy[i];int nexkey=getkey(xx,yy);int dd=nowkey|nexkey;if(e[u.x][u.y][xx][yy]==-1||xx<1||xx>n||yy<1||yy>m||(e[u.x][u.y][xx][yy]>0&&!(nowkey&(1<<(e[u.x][u.y][xx][yy]-1)))))continue;if(!vis[xx][yy][dd]){vis[xx][yy][dd]=1;q.push((node){xx,yy,dd,u.dis+1});}}}return -1;
}
int main(){cin>>n>>m>>p;cin>>k;for(int i=1;i<=k;i++){int x1,y1,x2,y2,g;cin>>x1>>y1>>x2>>y2>>g;if(g){e[x1][y1][x2][y2]=g;e[x2][y2][x1][y1]=g;}else{e[x1][y1][x2][y2]=-1;e[x2][y2][x1][y1]=-1;}}cin>>s;for(int i=1;i<=s;i++){int x,y,q;cin>>x>>y>>q;key[x][y][++cnt[x][y]]=q;}printf("%d",bfs(1,1));return 0;
}

2.P2756 飞行员配对方案问题

输出配对方案即可

纯板子题

匈牙利完直接输出

#include <cstdio>
#include <cstring>
using namespace std;
const int maxn=2e3+5;
int n,m,k;
int e[maxn][maxn];
int match[maxn],vis[maxn];
bool dfs(int u){for(int v=m+1;v<=n;v++){if(vis[v]||!e[u][v])continue;vis[v]=1;if(!match[v]||dfs(match[v])){// 如果v没有匹配,或者v的匹配找到了新的匹配match[v]=u;match[u]=v;          // 更新匹配信息return true;}}return false;
}
int xyl(){int ans=0;memset(match,0,sizeof(match));for(int i=1;i<=n;i++){// 对每一个点尝试匹配memset(vis,0,sizeof(vis));if(dfs(i))ans++;}return ans;
}
inline void init(){memset(e,0,sizeof(e));
}
int main(){scanf("%d%d",&m,&n);int u,v;while(scanf("%d%d",&u,&v)){if(u==-1&&v==-1)break;e[u][v]=1;e[v][u]=1;}printf("%d\n",xyl());for(int i=1;i<=m;i++){if(match[i]){printf("%d %d\n",i,match[i]);}}return 0;
}

3.P2761 软件补丁问题

题意:对于每一个补丁 iii,都有 2 个与之相应的错误集合 B1[i]B1[i]B1[i]和 B2[i]B2[i]B2[i],使得仅当软件包含 B1[i]中的所有错误,而不包含 B2[i]B2[i]B2[i]中的任何错误时,才可以使用补丁 iii。补丁 iii 将修复软件中的某些错误 F1[i]F1[i]F1[i],而同时加入另一些错误 F2[i]F2[i]F2[i]。另外,每个补丁都耗费一定的时间。

思路:数据范围和状态表示暗示要状压,可是dp会有后效性,所以spfaspfaspfa,因为边权并非全相等,用普通bfsbfsbfs的话不一定选到的是最优方式,重载优先队列的话可以达到同样效果

注意:如果暴力加上所有边的话边数会过多,直接在spfaspfaspfa的过程中进行状态转移

#include<bits/stdc++.h>
using namespace std;
const int maxn=25;
int b1[1<<22],b2[1<<22],f2[1<<22],f1[1<<22];
int t[105];
int n,m;
int inque[1<<22],dis[1<<22];
inline void spfa(int s){memset(dis,0x3f,sizeof dis);memset(inque,0,sizeof inque);dis[s]=0;queue<int>q;q.push(s);inque[s]=1;while(!q.empty()){int u=q.front();q.pop();inque[u]=0;for(int i=1;i<=m;i++){if((u&b1[i])==b1[i]&&!(u&b2[i])){int v=(u|f1[i])^f1[i]|f2[i];if(dis[v]>dis[u]+t[i]){dis[v]=dis[u]+t[i];if(!inque[v]){inque[v]=1;q.push(v);}}}}}cout<<(dis[0]>=0x3f3f3f3f/2?0:dis[0]);return;
}
signed main(){cin>>n>>m;for(int i=1;i<=m;i++){scanf("%d",&t[i]);scanf(" ");char x;for(int j=1;j<=n;j++){scanf("%c",&x);if(x=='+')b1[i]|=(1<<j-1);else if(x=='-')b2[i]|=(1<<j-1);}scanf(" ");for(int j=1;j<=n;j++){scanf("%c",&x);if(x=='-')f1[i]|=(1<<j-1);else if(x=='+')f2[i]|=(1<<j-1);}}int st=(1<<n)-1;spfa(st);return 0;
}

4.P4016 负载平衡问题

题意:GGG公司有 nnn 个沿铁路运输线环形排列的仓库,每个仓库存储的货物数量不等。如何用最少搬运量可以使 nnn 个仓库的库存数量相同。搬运货物时,只能在相邻的仓库之间搬运。

思路:
1.源点到每个仓库连上货量的流量,费用为000

2.仓库间两两连无限流量,费用为111的边

3.仓库到汇点连上流量为总货物的平均值,费用为0的边

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int inf=0x3f3f3f3f,maxn=1005,maxm=100005;
struct edge{int nex,v,cost,flow;
}e[maxm];
int head[maxn],cnt=1;
int n,m;
inline void add_edge(int u,int v,int cost,int flow){e[++cnt].v=v;e[cnt].cost=cost;e[cnt].flow=flow;e[cnt].nex=head[u];head[u]=cnt;e[++cnt].v=u;e[cnt].cost=-cost;e[cnt].flow=0;e[cnt].nex=head[v];head[v]=cnt;
}
int inque[maxn];
int dis[maxn],flow[maxn],pre[maxn],last[maxn];
bool spfa(int s,int t){queue<int>q;memset(dis,0x3f,sizeof dis);memset(flow,0x3f,sizeof flow);memset(inque,0,sizeof inque);q.push(s);inque[s]=1;dis[s]=0;pre[t]=-1;while(!q.empty()){int u=q.front();q.pop();inque[u]=0;for(int i=head[u];i;i=e[i].nex){int v=e[i].v;if(dis[v]>dis[u]+e[i].cost&&e[i].flow){dis[v]=dis[u]+e[i].cost;pre[v]=u;last[v]=i;flow[v]=min(flow[u],e[i].flow);if(!inque[v]){q.push(v);inque[v]=1;}}}}if(pre[t]!=-1)return true;return false;
}
int maxflow,mincost;
void mcmf(int s,int t){maxflow=0;mincost=0;while(spfa(s,t)){int u=t;
//      printf("maxflow==%d %d %d\n",maxflow,flow[t],flow[t]*dis[t]);maxflow+=flow[t];mincost+=flow[t]*dis[t];while(u!=s){e[last[u]].flow-=flow[t];e[last[u]^1].flow+=flow[t];u=pre[u];}}return;
}
signed main(){cin>>n;int s=n+1;int t=n+2;int x=0,sum=0;for(int i=1;i<=n;i++){cin>>x;sum+=x;add_edge(s,i,0,x);if(i!=n){add_edge(i,i+1,1,inf);}else{add_edge(n,1,1,inf);}if(i!=1){add_edge(i,i-1,1,inf);}else{add_edge(1,n,1,inf);}}int av=sum/n;for(int i=1;i<=n;i++){add_edge(i,t,0,av);}mcmf(s,t);cout<<mincost;return 0;
}

5.P3358 最长k可重区间集问题

6.P4014 分配问题

题意:nnn 件工作要分配给 nnn 个人做。第 iii 个人做第 jjj 件工作产生的效益为 cijc_{ij}cij​ 。试设计一个将 nnn 件工作分配给 nnn 个人做的分配方案,使产生的总效益最大。

思路:给每个人分配到最大价值的工作,二分图最大权匹配,裸kmkmkm即可

#include<bits/stdc++.h>
using namespace std;
#define int long long
const long long maxn=1005,inf=1e18;
int n,m,mp[maxn][maxn],matched[maxn];
int slack[maxn],ex[maxn],ey[maxn],pre[maxn];
int visx[maxn],visy[maxn];
inline void init(){memset(slack,0,sizeof slack);memset(ex,0,sizeof ex);memset(ey,0,sizeof ey);memset(pre,0,sizeof pre);memset(matched,0,sizeof matched);memset(visx,0,sizeof visx);memset(visy,0,sizeof visy);
}
void match(int u){int x,y=0,yy=0,d;memset(pre,0,sizeof(pre));for(int i=1;i<=n;i++)slack[i]=inf;matched[y]=u;while(1){x=matched[y];d=inf;visy[y]=1;for(int i=1;i<=n;i++){if(visy[i])continue;if(slack[i]>ex[x]+ey[i]-mp[x][i]){slack[i]=ex[x]+ey[i]-mp[x][i];pre[i]=y;}if(slack[i]<d){d=slack[i];yy=i;}}for(int i=0;i<=n;i++){if(visy[i])ex[matched[i]]-=d,ey[i]+=d;else slack[i]-=d;}y=yy;if(matched[y]==-1)break;}while(y){matched[y]=matched[pre[y]];y=pre[y];}
}
int km(){memset(matched,-1,sizeof(matched));memset(ex,0,sizeof(ex));memset(ey,0,sizeof(ey));for(int i=1;i<=n;i++){memset(visy,0,sizeof(visy));match(i);}int ans=0;for(int i=1;i<=n;i++){if(matched[i]!=-1)ans+=mp[matched[i]][i];}return ans;
}
signed main(){  scanf("%lld",&n);for(int i=1;i<=n;i++)for(int j=1;j<=n;j++)mp[i][j]=-inf;for(int i=1;i<=n;i++){for(int j=1;j<=n;j++){scanf("%lld",&mp[i][j]);}}int mm=km();init();for(int i=1;i<=n;i++){for(int j=1;j<=n;j++){mp[i][j]=-mp[i][j];}}for(int i=1;i<=n;i++)for(int j=1;j<=n;j++)if(mp[i][j]==-inf)mp[i][j]=inf;printf("%lld\n%lld",-km(),mm);return 0;
}

7.P4009 汽车加油行驶问题

题意:如果油不满,遇到加油站必须加满油付出代价a,如果回头走需要付出代价b,可以在任意一个地方建加油站付出代价c(神仙?)

思路:分层图最短路,记录每个点的剩余油量,遇到加油站且油不满直接加油并continuecontinuecontinue

#include<bits/stdc++.h>
using namespace std;
const int dx[]={1,-1,0,0};
const int dy[]={0,0,1,-1};
int n,m,k,a,b,c;
int vis[105][105][12];
int mp[105][105];
int dis[105][105][12],inque[105][105][12];
struct node{int x,y,res;
};
inline void spfa(int sx,int sy){memset(dis,0x3f,sizeof dis);dis[sx][sy][k]=0;queue<node>q;q.push((node){sx,sy,k});inque[sx][sy][k]=1;while(!q.empty()){node u=q.front();q.pop();int x=u.x;int y=u.y;int res=u.res;inque[x][y][res]=0;if(mp[x][y]&&res!=k){if(dis[x][y][k]>dis[x][y][res]+a){dis[x][y][k]=dis[x][y][res]+a;if(!inque[x][y][k]){inque[x][y][k]=1;q.push((node){x,y,k});}}continue;}else{if(dis[x][y][k]>dis[x][y][res]+a+c){dis[x][y][k]=dis[x][y][res]+a+c;if(!inque[x][y][k]){inque[x][y][k]=1;q.push((node){x,y,k});}}}if(res>0){for(int i=0;i<4;i++){int xx=x+dx[i];int yy=y+dy[i];int f=0;if(xx<x||yy<y)f=b;if(xx<1||yy<1||xx>n||yy>n)continue;if(dis[xx][yy][res-1]>dis[x][y][res]+f){dis[xx][yy][res-1]=dis[x][y][res]+f;if(!inque[xx][yy][res-1]){inque[xx][yy][res-1]=1;q.push((node){xx,yy,res-1});}}}}}
}
int main(){cin>>n>>k>>a>>b>>c;for(int i=1;i<=n;i++){for(int j=1;j<=n;j++){cin>>mp[i][j];}}spfa(1,1);int mm=0x3f3f3f3f;for(int i=0;i<=k;i++){mm=min(mm,dis[n][n][i]);}cout<<mm;return 0;
}

8.P4015 运输问题

题意:设计一个将仓库中所有货物运送到零售商店的运输方案,输出最小运输费用和最大运输费用

思路:二分图,分别跑一次最大最小费用流即可

#include<bits/stdc++.h>
using namespace std;
typedef pair<int,int> pii;
const int inf=0x3f3f3f3f,maxn=40005,maxm=400005;
struct edge{int nex,v,cost,flow;
}e[maxm];
int head[maxn],cnt=1;
int n,m,k,T;
inline void add_edge(int u,int v,int cost,int flow){e[++cnt].v=v;e[cnt].cost=cost;e[cnt].flow=flow;e[cnt].nex=head[u];head[u]=cnt;e[++cnt].v=u;e[cnt].cost=-cost;e[cnt].flow=0;e[cnt].nex=head[v];head[v]=cnt;
}
int inque[maxn],dis[maxn],flow[maxn],pre[maxn],last[maxn];
bool spfa(int s,int t){queue<int>q;memset(dis,0x3f,sizeof dis);memset(flow,0x3f,sizeof flow);memset(inque,0,sizeof inque);q.push(s);inque[s]=1;dis[s]=0;pre[t]=-1;while(!q.empty()){int u=q.front();q.pop();inque[u]=0;for(int i=head[u];i;i=e[i].nex){int v=e[i].v;if(dis[v]>dis[u]+e[i].cost&&e[i].flow){dis[v]=dis[u]+e[i].cost;pre[v]=u;last[v]=i;flow[v]=min(flow[u],e[i].flow);if(!inque[v]){q.push(v);inque[v]=1;}}}}if(pre[t]!=-1)return true;return false;
}
unordered_map<string,int>mp;
inline pair<int,int> mcmf(int s,int t){int maxflow=0;int mincost=0;while(spfa(s,t)){int u=t;maxflow+=flow[t];mincost+=flow[t]*dis[t];while(u!=s){e[last[u]].flow-=flow[t];e[last[u]^1].flow+=flow[t];u=pre[u];}}return {maxflow,mincost};
}
inline int id(int i,int j){return (i-1)*n+j;
}
int a[50005],b[50005],x[50005];
int main(){cin>>m>>n;int s=n+m+1;int t=n+m+2;for(int i=1;i<=m;i++){scanf("%d",&a[i]);add_edge(s,i,0,a[i]);}for(int i=1;i<=n;i++){scanf("%d",&b[i]);add_edge(i+m,t,0,b[i]);}for(int i=1;i<=m;i++){for(int j=1;j<=n;j++){scanf("%d",&x[id(i,j)]);add_edge(i,j+m,x[id(i,j)],a[i]);}}pii yaoyao=mcmf(s,t);cout<<yaoyao.second<<endl;cnt=1;memset(head,0,sizeof head);for(int i=1;i<=m;i++){add_edge(s,i,0,a[i]);}for(int i=1;i<=n;i++){add_edge(i+m,t,0,b[i]);}for(int i=1;i<=m;i++){for(int j=1;j<=n;j++){add_edge(i,j+m,-x[id(i,j)],a[i]);}}pii yaoyao2=mcmf(s,t);cout<<-yaoyao2.second<<endl;return 0;
}

9.P2774 方格取数问题

题意:选出一些数,这些数不能相邻,怎么取能使所取的数总和最大

思路:
首先通过观察可以发现,相邻的格子是一定不能取的

先把整个图进行染色

选取一些点,逆向思维可以转化为一开始全选,然后舍弃一些点,于是想到最小割

源点向所有白点连边,黑点向所有汇点连边,相邻的黑白点连容量为infinfinf的边

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int inf =0x3f3f3f3f,maxn=1e5+5,maxm=4e5+5;
int cur[maxn],head[maxn],dep[maxn];
const int dx[]={1,-1,0,0};
const int dy[]={0,0,-1,1};
int cnt=1,n,m,s,t;
struct edge{int v,w,nex;
}e[maxm*2];
inline void add_edge(int u,int v,int w){e[++cnt].v=v;e[cnt].w=w;e[cnt].nex=head[u];head[u]=cnt;e[++cnt].v=u;e[cnt].w=0;e[cnt].nex=head[v];head[v]=cnt;
}
bool bfs(){memset(dep,0,sizeof(dep));for(int i=0;i<=t+5;i++){//如果要拆点或者其他的一定记得把范围开大点!!! cur[i]=head[i];}dep[s]=1;queue<int>q;q.push(s);while(!q.empty()){int u=q.front();q.pop();for(int i=head[u];i;i=e[i].nex){int v=e[i].v;if(!dep[v]&&e[i].w){dep[v]=dep[u]+1;q.push(v);if(v==t)return true;        }}}return false;
}
int dfs(int u,int now){if(u==t||now==0){return now;}int flow=0,rlow=0;for(int i=cur[u];i;i=e[i].nex){int v=e[i].v;if(e[i].w&&dep[v]==dep[u]+1){if(rlow=dfs(v,min(now,e[i].w))){flow+=rlow;now-=rlow;e[i].w-=rlow;e[i^1].w+=rlow;if(now==0)return flow;}}}if(!flow)dep[u]=-1;return flow;
}
int dinic(){int maxflow=0;while(bfs()){maxflow+=dfs(s,inf);}return maxflow;
}
inline int id(int x,int y){return (x-1)*m+y;
}
int f[maxn],se[maxn],sum;
signed main(){scanf("%lld%lld",&n,&m);int u,v,w;s=0,t=n*m+1;for(int i=1;i<=n;i++){for(int j=1;j<=m;j++){scanf("%lld",&f[id(i,j)]);sum+=f[id(i,j)];if((i+j)%2==1){se[id(i,j)]=1;add_edge(s,id(i,j),f[id(i,j)]);}else if((i+j)%2==0){add_edge(id(i,j),t,f[id(i,j)]);}}}for(int i=1;i<=n;i++){for(int j=1;j<=m;j++){if((i+j)%2==1){int dd=id(i,j);int ff=f[dd];for(int k=0;k<4;k++){int xx=i+dx[k];int yy=j+dy[k];if(xx>=1&&yy<=m&&xx<=n&&yy>=1){add_edge(id(i,j),id(xx,yy),inf);}}}}}printf("%lld",sum-dinic());return 0;
}

10.P4012 深海机器人问题

题意:k个机器人从出发点到终点路径上的最大价值是多少,每个格子只能取一次

思路:直接按照题意建图即可

源点连一条容量为KKK到出发点的边,表示有KKK个机器人

拆点:
连一条边流量为111,费用www,代表只能取走一次价值

连一条边流量infinfinf,费用000,代表每个点可以走无限次

#include<bits/stdc++.h>
using namespace std;
typedef pair<int,int> pii;
const int inf=0x3f3f3f3f,maxn=20005,maxm=20005;
struct edge{int nex,v,cost,flow;
}e[maxm];
int head[maxn],cnt=1;
int n,m,k,T;
inline void add_edge(int u,int v,int cost,int flow){e[++cnt].v=v;e[cnt].cost=cost;e[cnt].flow=flow;e[cnt].nex=head[u];head[u]=cnt;e[++cnt].v=u;e[cnt].cost=-cost;e[cnt].flow=0;e[cnt].nex=head[v];head[v]=cnt;
}
int inque[maxn],dis[maxn],flow[maxn],pre[maxn],last[maxn];
bool spfa(int s,int t){queue<int>q;memset(dis,0x3f,sizeof dis);memset(flow,0x3f,sizeof flow);memset(inque,0,sizeof inque);q.push(s);inque[s]=1;dis[s]=0;pre[t]=-1;while(!q.empty()){int u=q.front();q.pop();inque[u]=0;for(int i=head[u];i;i=e[i].nex){int v=e[i].v;if(dis[v]>dis[u]+e[i].cost&&e[i].flow){dis[v]=dis[u]+e[i].cost;pre[v]=u;last[v]=i;flow[v]=min(flow[u],e[i].flow);if(!inque[v]){q.push(v);inque[v]=1;}}}}if(pre[t]!=-1)return true;return false;
}
inline pair<int,int> mcmf(int s,int t){int maxflow=0;int mincost=0;while(spfa(s,t)){int u=t;maxflow+=flow[t];mincost+=flow[t]*dis[t];while(u!=s){e[last[u]].flow-=flow[t];e[last[u]^1].flow+=flow[t];u=pre[u];}}return {maxflow,mincost};
}
int p,q,a,b;
inline int id(int x,int y){return x*(q+1)+y+1;
}
int main(){cin>>a>>b>>p>>q;int s=(p+1)*(q+1)+1;int t=s+1;for(int i=0;i<p+1;i++){for(int j=0;j<q;j++){int w;cin>>w;add_edge(id(i,j),id(i,j+1),-w,1);add_edge(id(i,j),id(i,j+1),0,inf);}}for(int j=0;j<q+1;j++){for(int i=0;i<p;i++){int w;cin>>w;add_edge(id(i,j),id(i+1,j),-w,1);add_edge(id(i,j),id(i+1,j),0,inf);}}for(int i=1;i<=a;i++){int f,x,y;cin>>f>>x>>y;add_edge(s,id(x,y),0,f);}for(int i=1;i<=b;i++){int f,x,y;cin>>f>>x>>y;add_edge(id(x,y),t,0,f);}pii yaoyao=mcmf(s,t);cout<<-yaoyao.second;
}

11.P2762 太空飞行计划问题

题意:有一些实验可以获利,需要花钱买一些仪器才能完成,问净收益最大的试验计划

思路:
最大权闭合子图

建二分图,实验在左边,仪器在右边

割掉左边的边代表不选择这个实验,放弃它的价值,割右边的边代表选择付出这个代价,用价值总和-最小割即为答案

(对最大权闭合子图的理解已经在之前的博客写过,不再赘述)

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int inf =0x3f3f3f3f,maxn=1e5+5,maxm=4e5+5;
int cur[maxn],head[maxn],dep[maxn];
vector<int>yq,sy;
const int dx[]={1,-1,0,0};
const int dy[]={0,0,-1,1};
int cnt=1,n,m,s,t,qq;
inline int read(int &x){char c;x=0;while(c<'0'||c>'9')c=getchar();while(c>='0'&&c<='9'){x=x*10+c-'0';c=getchar();}return c=='\r'||c=='\n'?0:1;
}
struct edge{int v,w,nex;
}e[maxm*2];
inline void add_edge(int u,int v,int w){e[++cnt].v=v;e[cnt].w=w;e[cnt].nex=head[u];head[u]=cnt;e[++cnt].v=u;e[cnt].w=0;e[cnt].nex=head[v];head[v]=cnt;
}
bool bfs(){memset(dep,0,sizeof(dep));for(int i=0;i<=t+5;i++){//如果要拆点或者其他的一定记得把范围开大点!!! cur[i]=head[i];}dep[s]=1;queue<int>q;q.push(s);while(!q.empty()){int u=q.front();q.pop();for(int i=head[u];i;i=e[i].nex){int v=e[i].v;if(!dep[v]&&e[i].w){dep[v]=dep[u]+1;q.push(v);if(v==t)return true;        }}}return false;
}
int dfs(int u,int now){if(u==t||now==0){return now;}int flow=0,rlow=0;for(int i=cur[u];i;i=e[i].nex){int v=e[i].v;if(e[i].w&&dep[v]==dep[u]+1){if(rlow=dfs(v,min(now,e[i].w))){flow+=rlow;now-=rlow;e[i].w-=rlow;e[i^1].w+=rlow;if(v>=m+1&&v<=m+n){sy.push_back(u);yq.push_back(v);}if(now==0)return flow;}}}if(!flow)dep[u]=-1;return flow;
}
int dinic(){int maxflow=0;while(bfs()){maxflow+=dfs(s,inf);}return maxflow;
}
inline int dd(int x,int y){return (x-1)*m+y;
}
int f[maxn],id[maxn],sum;
signed main(){scanf("%lld%lld",&m,&n);int u,v,w;s=0,t=n+m+1;for(int i=1;i<=m;i++){qq=read(w);add_edge(s,i,w);sum+=w;while(qq){qq=read(w);add_edge(i,m+w,inf);}}for(int i=1;i<=n;i++){scanf("%lld",&w);add_edge(m+i,t,w);}int yaoyao=dinic();for(int i=1;i<=m;i++){if(dep[i])printf("%d ",i);}printf("\n");for(int i=1;i<=n;i++){if(dep[i+m])printf("%d ",i);}printf("\n%lld",sum-yaoyao);return 0;
}

12.P2770 航空路线问题

题意:从起点到终点走一个来回,求最多能经过多少个城市(起点终点可经过两次)

思路:
因为求一来一回有点不方便,先将边转化为全部去终点,转化成求起点去终点的两条不相交路径

先跑一发费用流,求出最大经过城市数量

终点在于怎么输出路径,这里利用走过的边流量为000的性质,跑两次dfsdfsdfs即可

#include<bits/stdc++.h>
using namespace std;
typedef pair<int,int> pii;
const int inf=0x3f3f3f3f,maxn=40005,maxm=400005;
struct edge{int nex,v,cost,flow;
}e[maxm];
int head[maxn],cnt=1;
int n,m,k,T;
inline void add_edge(int u,int v,int cost,int flow){e[++cnt].v=v;e[cnt].cost=cost;e[cnt].flow=flow;e[cnt].nex=head[u];head[u]=cnt;e[++cnt].v=u;e[cnt].cost=-cost;e[cnt].flow=0;e[cnt].nex=head[v];head[v]=cnt;
}
int inque[maxn],dis[maxn],flow[maxn],pre[maxn],last[maxn];
bool spfa(int s,int t){queue<int>q;memset(dis,0x3f,sizeof dis);memset(flow,0x3f,sizeof flow);memset(inque,0,sizeof inque);q.push(s);inque[s]=1;dis[s]=0;pre[t]=-1;while(!q.empty()){int u=q.front();q.pop();inque[u]=0;for(int i=head[u];i;i=e[i].nex){int v=e[i].v;if(dis[v]>dis[u]+e[i].cost&&e[i].flow){dis[v]=dis[u]+e[i].cost;pre[v]=u;last[v]=i;flow[v]=min(flow[u],e[i].flow);if(!inque[v]){q.push(v);inque[v]=1;}}}}if(pre[t]!=-1)return true;return false;
}
string str[105];
unordered_map<string,int>mp;
inline pair<int,int> mcmf(int s,int t){int maxflow=0;int mincost=0;int tim=0;while(spfa(s,t)){int u=t;maxflow+=flow[t];mincost+=flow[t]*dis[t];++tim;while(u!=s){e[last[u]].flow-=flow[t];e[last[u]^1].flow+=flow[t];u=pre[u];}}return {maxflow,mincost};
}
int vis[1005];
vector<string>jl1,jl2;
void dfs1(int u){jl1.push_back(str[u]);
//  cout<<str[u]<<endl;vis[u]=1;for(int i=head[u+n];i;i=e[i].nex){int v=e[i].v;if(e[i].flow!=0||vis[v]==1||v>n||v<1)continue;else{dfs1(v);break;}}
}
void dfs2(int u){//  vis[u]=1;jl2.push_back(str[u]);for(int i=head[u+n];i;i=e[i].nex){int v=e[i].v;if(e[i].flow!=0||vis[v]==1||v>n||v<1)continue;dfs2(v);}
//  cout<<str[u]<<endl;
}
int main(){cin>>n>>m;int num=0;int s=1;int t=n*2;bool ok=false;for(int i=1;i<=n;i++){string a;cin>>a;mp[a]=i;str[i]=a;if(i==1||i==n){add_edge(i,i+n,-1,2);}else{add_edge(i,i+n,-1,1);}}for(int i=1;i<=m;i++){string a,b;cin>>a>>b;int x=mp[a];int y=mp[b];if(x>y)swap(x,y);if(x==1&&y==n)ok=true;add_edge(x+n,y,0,1);}pii yaoyao=mcmf(s,t);if(yaoyao.first==2){printf("%d\n",-yaoyao.second-2);dfs1(s);dfs2(s);for(auto v:jl1){cout<<v<<endl;}string zz[105];int numzz=0;for(auto v:jl2){zz[++numzz]=v;}for(int i=numzz;i>=1;i--){cout<<zz[i]<<endl;}}else if(yaoyao.first==1&&ok){cout<<2<<endl;cout<<str[1]<<endl;cout<<str[n]<<endl;cout<<str[1]<<endl;}else{printf("No Solution!");}return 0;}

13.P2754 [CTSC1999]家园 / 星际转移问题

题意:有n个太空站,m艘飞船按照一定路线来回飞行,求地球到月球的最短时间

思路:
分层图网络流

每过一天建一套站点,借用一下luogu大佬题解的图片

进行解释一下,样例的走法是:
d1d1d1:到111号点
d2d2d2:到222号点
d3d3d3:不走
d4d4d4:不走
d5d5d5:走到月球

因为飞船是不会停下来的,有的时候就必须在原地飞船走到这个路线

每一次循环建立一套如图所示的站点

飞船的路线之间建立容量为capcapcap的边,代表飞船一次能带这么多的人

前一天的这个点和后一天的这个点建立容量为infinfinf的边(代表所有人都可以继续在这个站点等飞船过来)

然后不断跑最大流,直到sum>=ksum>=ksum>=k

设定一个较大的天数,如果超过了就break,代表不能将所有人都运输到月球

#include<bits/stdc++.h>
using namespace std;
const int inf =0x3f3f3f3f,maxn=1e5+5,maxm=4e5+5;
int cur[maxn],head[maxn],dep[maxn],cap[maxn],cz[maxn],mp[205][205];
int cnt=1,n,m,s,t,k;
struct edge{int v,w,nex;
}e[maxm*2];
inline void add_edge(int u,int v,int w){e[++cnt].v=v;e[cnt].w=w;e[cnt].nex=head[u];head[u]=cnt;e[++cnt].v=u;e[cnt].w=0;e[cnt].nex=head[v];head[v]=cnt;
}
bool bfs(){memset(dep,0,sizeof(dep));memcpy(cur,head,sizeof head);dep[s]=1;queue<int>q;q.push(s);while(!q.empty()){int u=q.front();q.pop();for(int i=head[u];i;i=e[i].nex){int v=e[i].v;if(!dep[v]&&e[i].w){dep[v]=dep[u]+1;q.push(v);if(v==t)return true;       }}}return false;
}
int dfs(int u,int now){if(u==t||now==0){return now;}int flow=0,rlow=0;for(int i=cur[u];i;i=e[i].nex){int v=e[i].v;if(e[i].w&&dep[v]==dep[u]+1){if(rlow=dfs(v,min(now,e[i].w))){flow+=rlow;now-=rlow;e[i].w-=rlow;e[i^1].w+=rlow;if(now==0)return flow;}}}if(!flow)dep[u]=-1;return flow;
}
int dinic(){int maxflow=0;while(bfs()){maxflow+=dfs(s,inf);}return maxflow;
}
int main(){scanf("%d%d%d",&n,&m,&k);int u,v,w;n+=2;for(int i=1;i<=m;i++){scanf("%d%d",&cap[i],&cz[i]);for(int j=0;j<cz[i];j++){scanf("%d",&mp[i][j]);mp[i][j]+=2;}}int day=0;s=0,t=9999;int sum=0;while(day<500){add_edge(s,day*n+2,inf);add_edge(day*n+1,t,inf);if(day){for(int i=1;i<=n;i++){add_edge((day-1)*n+i,day*n+i,inf);}for(int i=1;i<=m;i++){int x=mp[i][(day-1)%cz[i]];int y=mp[i][day%cz[i]];add_edge((day-1)*n+x,day*n+y,cap[i]);}}sum+=dinic();if(sum>=k)break;day++;}if(day==500)printf("0");else printf("%d",day);return 0;
}

14.P3254 圆桌问题

题意:有n个单位,m张餐桌,求每个单位的人不跟同单位的人在一个餐桌的方案

思路:
源点向每个单位建一条容量为单位人数的边

每个餐桌向汇点建一条容量为餐桌容纳的边

每个单位向每张餐桌连一条容量为1的边,代表各单位只能一个人选择这张餐桌

跑最大流

利用走过的路径反向边流量为1的性质,跑dfsdfsdfs记录每个单位的人的选择即可

#include<bits/stdc++.h>
using namespace std;
const int inf =0x3f3f3f3f,maxn=1e5+5,maxm=4e5+5;
int cur[maxn],head[maxn],dep[maxn];
int cnt=1,n,m,s,t;
vector<int>jl[maxn];
struct edge{int v,w,nex;
}e[maxm*2];
inline void add_edge(int u,int v,int w){e[++cnt].v=v;e[cnt].w=w;e[cnt].nex=head[u];head[u]=cnt;e[++cnt].v=u;e[cnt].w=0;e[cnt].nex=head[v];head[v]=cnt;
}
bool bfs(){memset(dep,0,sizeof(dep));for(int i=1;i<=t+5;i++){//如果要拆点或者其他的一定记得把范围开大点!!! cur[i]=head[i];}dep[s]=1;queue<int>q;q.push(s);while(!q.empty()){int u=q.front();q.pop();for(int i=head[u];i;i=e[i].nex){int v=e[i].v;if(!dep[v]&&e[i].w){dep[v]=dep[u]+1;q.push(v);if(v==t)return true;        }}}return false;
}
int dfs(int u,int now){if(u==t||now==0){return now;}int flow=0,rlow=0;for(int i=cur[u];i;i=e[i].nex){int v=e[i].v;if(e[i].w&&dep[v]==dep[u]+1){if(rlow=dfs(v,min(now,e[i].w))){flow+=rlow;now-=rlow;e[i].w-=rlow;e[i^1].w+=rlow;if(now==0)return flow;}}}if(!flow)dep[u]=-1;return flow;
}
int dinic(){int maxflow=0;while(bfs()){maxflow+=dfs(s,inf);}return maxflow;
}
void dfs2(int u){for(int i=head[u];i;i=e[i].nex){int v=e[i].v;if(v==t)return;if(v>=1&&v<=n){dfs2(v);}if(e[i^1].w==1&&v>=n+1&&v<=n+m){jl[u].push_back(v-n);}}return;
}
int main(){int sum=0;cin>>n>>m;s=n+m+1;t=n+m+2;for(int i=1;i<=n;i++){int x;cin>>x;sum+=x;add_edge(s,i,x);}for(int i=1;i<=m;i++){int x;cin>>x;add_edge(i+n,t,x);}for(int i=1;i<=n;i++){for(int j=1;j<=m;j++){add_edge(i,j+n,1);}}int yaoyao=dinic();if(sum!=yaoyao)printf("0\n");else{dfs2(s);printf("1\n");for(int i=1;i<=n;i++){for(auto v:jl[i]){printf("%d ",v);}printf("\n");}}return 0;
}

15.P1251 餐巾计划问题

16.P2763 试题库问题

题意:假设一个试题库中有 nn 道试题。每道试题都标明了所属类别。同一道题可能有多个类别属性。现要从题库中抽取 mm 道题组成试卷。并要求试卷包含指定类型的试题。

思路:
每个类型向汇点建一条容量为此类型需要的题目数量的边即可

增广路的过程中记录这个类型选择的题目

#include<bits/stdc++.h>
using namespace std;
const int inf =0x3f3f3f3f,maxn=1e5+5,maxm=4e5+5;
int cur[maxn],head[maxn],dep[maxn],to[maxn],flag[maxn];
int cnt=1,n,m,s,t,k;
vector<int>jl[maxn];
struct edge{int v,w,nex;
}e[maxm*2];
inline void add_edge(int u,int v,int w){e[++cnt].v=v;e[cnt].w=w;e[cnt].nex=head[u];head[u]=cnt;e[++cnt].v=u;e[cnt].w=0;e[cnt].nex=head[v];head[v]=cnt;
}
bool bfs(){memset(dep,0,sizeof(dep));for(int i=0;i<=t+5;i++){//如果要拆点或者其他的一定记得把范围开大点!!! cur[i]=head[i];}dep[s]=1;queue<int>q;q.push(s);while(!q.empty()){int u=q.front();q.pop();for(int i=head[u];i;i=e[i].nex){int v=e[i].v;if(!dep[v]&&e[i].w){dep[v]=dep[u]+1;q.push(v);if(v==t)return true;        }}}return false;
}
int dfs(int u,int now){if(u==t||now==0){return now;}int flow=0,rlow=0;for(int i=cur[u];i;i=e[i].nex){int v=e[i].v;if(e[i].w&&dep[v]==dep[u]+1){if(rlow=dfs(v,min(now,e[i].w))){flow+=rlow;now-=rlow;e[i].w-=rlow;e[i^1].w+=rlow;if(v>=n+1&&v<=n+k){jl[v-n].push_back(u);}if(now==0)return flow;}}}if(!flow)dep[u]=-1;return flow;
}
int dinic(){int maxflow=0;while(bfs()){maxflow+=dfs(s,inf);}return maxflow;
}
int main(){cin>>k>>n;s=0,t=n+k+1;int x;for(int i=1;i<=k;i++){scanf("%d",&x);add_edge(n+i,t,x);m+=x;}int p;for(int i=1;i<=n;i++){scanf("%d",&p);add_edge(0,i,1);for(int j=1;j<=p;j++){scanf("%d",&x);add_edge(i,n+x,1);}}int ss=dinic();if(ss<m){printf("No Solution!");}else{for(int i=1;i<=k;i++){printf("%d:",i);for(int j=0;j<jl[i].size();j++){printf(" %d",jl[i][j]);}printf("\n");}}return 0;
}

17.P2766 最长不下降子序列问题

题意:
task1task1task1:求lislislis

task2task2task2:每个点取一次,求有多少个长度为sss的lislislis

task3task3task3:首尾可以取无限次,求有多少个长度为sss的lislislis

思路:
task1task1task1:dpdpdp求lislislis,并记录每个点的值(到此为止的lislislis长度)

task2task2task2:每个点与lislislis为相邻长度的点连边,跑出最大流即为答案

task3task3task3:在前一问的条件下把首尾点容量改为无限即可

#include<bits/stdc++.h>
using namespace std;
const int inf =0x3f3f3f3f,maxn=1e5+5,maxm=4e5+5;
int cur[maxn],head[maxn],dep[maxn];
int cnt=1,n,m,s,t,ss,ll;
struct edge{int v,w,nex;
}e[maxm*2];
inline void add_edge(int u,int v,int w){e[++cnt].v=v;e[cnt].w=w;e[cnt].nex=head[u];head[u]=cnt;e[++cnt].v=u;e[cnt].w=0;e[cnt].nex=head[v];head[v]=cnt;
}
bool bfs(){memset(dep,0,sizeof(dep));memcpy(cur,head,sizeof head);dep[s]=1;queue<int>q;q.push(s);while(!q.empty()){int u=q.front();q.pop();for(int i=head[u];i;i=e[i].nex){int v=e[i].v;if(!dep[v]&&e[i].w){dep[v]=dep[u]+1;q.push(v);if(v==t)return true;       }}}return false;
}
int dfs(int u,int now){if(u==t||now==0){return now;}int flow=0,rlow=0;for(int i=cur[u];i;i=e[i].nex){int v=e[i].v;if(e[i].w&&dep[v]==dep[u]+1){if(rlow=dfs(v,min(now,e[i].w))){flow+=rlow;now-=rlow;e[i].w-=rlow;e[i^1].w+=rlow;if(now==0)return flow;}}}if(!flow)dep[u]=-1;return flow;
}
int dinic(){int maxflow=0;while(bfs()){maxflow+=dfs(s,inf);}return maxflow;
}
int a[maxn],dp[maxn];
inline void init(){add_edge(s,1,inf);add_edge(1,1+n,inf);if(dp[n]==ll){add_edge(n+n,t,inf);add_edge(n,n+n,inf);}
}
int main(){scanf("%d",&n);int u,v,w;s=0,t=n+n+1;for(int i=1;i<=n;i++){scanf("%d",&a[i]);dp[i]=1;}for(int i=1;i<=n;i++){for(int j=1;j<i;j++){if(a[j]<=a[i]){dp[i]=max(dp[i],dp[j]+1);}}}for(int i=1;i<=n;i++){ll=max(ll,dp[i]);
//      cout<<dp[i]<<" ";}cout<<ll<<endl;for(int i=1;i<=n;i++){add_edge(i,i+n,1);if(dp[i]==1){add_edge(s,i,1);}if(dp[i]==ll){add_edge(i+n,t,1);}for(int j=1;j<i;j++){if(a[j]<=a[i]&&dp[j]+1==dp[i]){add_edge(j+n,i,1);}}}int ff=dinic();printf("%d\n",ff);init();ff+=dinic();printf("%d\n",ff<1e9?ff:1);return 0;
}

18.P3355 骑士共存问题

题意:有一些障碍,计算棋盘上最多可以放置多少个骑士,使得它们彼此互不攻击

思路:
这题跟方格取数那一题的思想是一样的,每个点有与它限制的位置的点,但是有有所不同,这一题本质上是求最大独立集(因为互相不能攻击到),而那一题有点权,是求最小割

染色后将能互相攻击的位置连边,求出最大匹配

总点数-障碍数-最大匹配=最大独立集

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int inf =0x3f3f3f3f,maxn=2e5+5,maxm=4e5+5;
int cur[maxn],head[maxn],dep[maxn];
const int dx[]={1,1,-1,-1,2,-2,2,-2};
const int dy[]={2,-2,2,-2,1,1,-1,-1};
int cnt=1,n,m,s,t;
struct edge{int v,w,nex;
}e[maxm*2];
inline void add_edge(int u,int v,int w){e[++cnt].v=v;e[cnt].w=w;e[cnt].nex=head[u];head[u]=cnt;e[++cnt].v=u;e[cnt].w=0;e[cnt].nex=head[v];head[v]=cnt;
}
bool bfs(){memset(dep,0,sizeof(dep));for(int i=0;i<=t+5;i++){//如果要拆点或者其他的一定记得把范围开大点!!! cur[i]=head[i];}dep[s]=1;queue<int>q;q.push(s);while(!q.empty()){int u=q.front();q.pop();for(int i=head[u];i;i=e[i].nex){int v=e[i].v;if(!dep[v]&&e[i].w){dep[v]=dep[u]+1;q.push(v);if(v==t)return true;        }}}return false;
}
int dfs(int u,int now){if(u==t||now==0){return now;}int flow=0,rlow=0;for(int i=cur[u];i;i=e[i].nex){int v=e[i].v;if(e[i].w&&dep[v]==dep[u]+1){if(rlow=dfs(v,min(now,e[i].w))){flow+=rlow;now-=rlow;e[i].w-=rlow;e[i^1].w+=rlow;if(now==0)return flow;}}}if(!flow)dep[u]=-1;return flow;
}
int dinic(){int maxflow=0;while(bfs()){maxflow+=dfs(s,inf);}return maxflow;
}
inline int id(int x,int y){return (x-1)*n+y;
}
int mp[505][505];
signed main(){scanf("%lld%lld",&n,&m);int u,v,w;s=0,t=n*n+1;for(int i=1;i<=m;i++){int x,y;cin>>x>>y;mp[x][y]=1;}for(int i=1;i<=n;i++){for(int j=1;j<=n;j++){if(mp[i][j])continue;if((i+j)%2==1){add_edge(s,id(i,j),1);}else if((i+j)%2==0){add_edge(id(i,j),t,1);}}}for(int i=1;i<=n;i++){for(int j=1;j<=n;j++){if((i+j)%2==1&&!mp[i][j]){for(int k=0;k<8;k++){int xx=i+dx[k];int yy=j+dy[k];if(xx>=1&&yy<=n&&xx<=n&&yy>=1){add_edge(id(i,j),id(xx,yy),inf);}}}}}printf("%lld",n*n-m-dinic());return 0;
}

19.P3357 最长k可重线段集问题

20.P4013 数字梯形问题

task1task1task1:不允许有路径相交:
很简单,一看就是点和边流量限制都为1,这样就不可能有右边的流跨到左边去了

task2task2task2:允许选重复的点:
点的流量限制无限即可,主要点跟汇点的流量也要改掉

task3task3task3:允许路径相交,允许重复点:
点边都无限即可

建图三次

#include<bits/stdc++.h>
using namespace std;
typedef pair<int,int> pii;
const int inf=0x3f3f3f3f,maxn=40005,maxm=400005;
struct edge{int nex,v,cost,flow;
}e[maxm];
int head[maxn],cnt=1;
int n,m,k,T;
inline void add_edge(int u,int v,int cost,int flow){e[++cnt].v=v;e[cnt].cost=cost;e[cnt].flow=flow;e[cnt].nex=head[u];head[u]=cnt;e[++cnt].v=u;e[cnt].cost=-cost;e[cnt].flow=0;e[cnt].nex=head[v];head[v]=cnt;
}
int inque[maxn],dis[maxn],flow[maxn],pre[maxn],last[maxn];
bool spfa(int s,int t){queue<int>q;memset(dis,0x3f,sizeof dis);memset(flow,0x3f,sizeof flow);memset(inque,0,sizeof inque);q.push(s);inque[s]=1;dis[s]=0;pre[t]=-1;while(!q.empty()){int u=q.front();q.pop();inque[u]=0;for(int i=head[u];i;i=e[i].nex){int v=e[i].v;if(dis[v]>dis[u]+e[i].cost&&e[i].flow){dis[v]=dis[u]+e[i].cost;pre[v]=u;last[v]=i;flow[v]=min(flow[u],e[i].flow);if(!inque[v]){q.push(v);inque[v]=1;}}}}if(pre[t]!=-1)return true;return false;
}
inline pair<int,int> mcmf(int s,int t){int maxflow=0;int mincost=0;while(spfa(s,t)){int u=t;maxflow+=flow[t];mincost+=flow[t]*dis[t];while(u!=s){e[last[u]].flow-=flow[t];e[last[u]^1].flow+=flow[t];u=pre[u];}}return {maxflow,mincost};
}
inline int id(int x,int y){return (x-1)*(m+n-1)+y;
}
int mp[1000][1000];
int main(){cin>>m>>n;int num=10000;int k=m-1;int s=20001;int t=20002;for(int i=1;i<=n;i++){k++;for(int j=1;j<=k;j++){cin>>mp[i][j];add_edge(id(i,j),id(i,j)+num,-mp[i][j],1);if(i==1){add_edge(s,id(i,j),0,1);}if(i==n){add_edge(id(i,j)+num,t,0,1);}}}k=m-1;for(int i=1;i<=n-1;i++){k++;for(int j=1;j<=k;j++){add_edge(id(i,j)+num,id(i+1,j),0,1);add_edge(id(i,j)+num,id(i+1,j+1),0,1);}}pii yaoyao1=mcmf(s,t);cout<<-yaoyao1.second<<endl;memset(head,0,sizeof head);cnt=1;for(int i=1;i<=n;i++){k++;for(int j=1;j<=k;j++){//    cin>>mp[i][j];add_edge(id(i,j),id(i,j)+num,-mp[i][j],inf);if(i==1){add_edge(s,id(i,j),0,1);}if(i==n){add_edge(id(i,j)+num,t,0,inf);}}}k=m-1;for(int i=1;i<=n-1;i++){k++;for(int j=1;j<=k;j++){add_edge(id(i,j)+num,id(i+1,j),0,1);add_edge(id(i,j)+num,id(i+1,j+1),0,1);}}pii yaoyao2=mcmf(s,t);cout<<-yaoyao2.second<<endl;memset(head,0,sizeof head);cnt=1;for(int i=1;i<=n;i++){k++;for(int j=1;j<=k;j++){// cin>>mp[i][j];add_edge(id(i,j),id(i,j)+num,-mp[i][j],inf);if(i==1){add_edge(s,id(i,j),0,1);}if(i==n){add_edge(id(i,j)+num,t,0,inf);}}}k=m-1;for(int i=1;i<=n-1;i++){k++;for(int j=1;j<=k;j++){add_edge(id(i,j)+num,id(i+1,j),0,inf);add_edge(id(i,j)+num,id(i+1,j+1),0,inf);}}pii yaoyao3=mcmf(s,t);cout<<-yaoyao3.second<<endl;return 0;
}

21.P3356 火星探险问题

题意:有一些障碍,有些点上面有矿石,有n辆车,只能向左或向下走,求采到最多矿石的路径

思路:建图跟深海那题一样,利用反向边流量的性质跑dfsdfsdfs记录路径即可

#include<bits/stdc++.h>
using namespace std;
typedef pair<int,int> pii;
const int inf=0x3f3f3f3f,maxn=20005,maxm=400005;
struct edge{int nex,v,cost,flow;
}e[maxm];
int head[maxn],cnt=1;
int n,m,k,T;
inline void add_edge(int u,int v,int cost,int flow){e[++cnt].v=v;e[cnt].cost=cost;e[cnt].flow=flow;e[cnt].nex=head[u];head[u]=cnt;e[++cnt].v=u;e[cnt].cost=-cost;e[cnt].flow=0;e[cnt].nex=head[v];head[v]=cnt;
}
int inque[maxn],dis[maxn],flow[maxn],pre[maxn],last[maxn];
bool spfa(int s,int t){queue<int>q;memset(dis,0x3f,sizeof dis);memset(flow,0x3f,sizeof flow);memset(inque,0,sizeof inque);q.push(s);inque[s]=1;dis[s]=0;pre[t]=-1;while(!q.empty()){int u=q.front();q.pop();inque[u]=0;for(int i=head[u];i;i=e[i].nex){int v=e[i].v;if(dis[v]>dis[u]+e[i].cost&&e[i].flow){dis[v]=dis[u]+e[i].cost;pre[v]=u;last[v]=i;flow[v]=min(flow[u],e[i].flow);if(!inque[v]){q.push(v);inque[v]=1;}}}}if(pre[t]!=-1)return true;return false;
}
inline pair<int,int> mcmf(int s,int t){int maxflow=0;int mincost=0;while(spfa(s,t)){int u=t;maxflow+=flow[t];mincost+=flow[t]*dis[t];while(u!=s){e[last[u]].flow-=flow[t];e[last[u]^1].flow+=flow[t];u=pre[u];}}return {maxflow,mincost};
}
inline int id(int x,int y){return (x-1)*m+y;
}
inline int id2(int x,int y){return (x-1)*m+y+n*m;
}
int vis[maxn];
void dfs(int u,int num){for(int i=head[u+n*m];i;i=e[i].nex){int v=e[i].v;if(v==u+1&&vis[i]<e[i^1].flow){printf("%d 1\n",num);vis[i]++;dfs(v,num);break;}if(v==u+m&&vis[i]<e[i^1].flow){printf("%d 0\n",num);vis[i]++;dfs(v,num);break;}}
}
int mp[105][105];
int main(){cin>>k>>m>>n;for(int i=1;i<=n;i++){for(int j=1;j<=m;j++){cin>>mp[i][j];if(mp[i][j]==1)continue;add_edge(id(i,j),id2(i,j),mp[i][j]==2?-1:0,1);add_edge(id(i,j),id2(i,j),0,inf);}}int s=n*m*2+1;int t=s+1;if(mp[1][1]!=1)add_edge(s,id(1,1),0,k);if(mp[n][m]!=1)add_edge(id2(n,m),t,0,k);for(int i=1;i<=n;i++){for(int j=1;j<=m;j++){if(mp[i][j]==1)continue;if(mp[i][j+1]!=1&&j+1<=m){add_edge(id2(i,j),id(i,j+1),0,inf);}if(mp[i+1][j]!=1&&i+1<=n){add_edge(id2(i,j),id(i+1,j),0,inf);}}}pii yaoyao=mcmf(s,t);for(int i=1;i<=k;i++){dfs(1,i);}return 0;
}

22.P2765 魔术球问题

题意:
假设有 nnn 根柱子,现要按下述规则在这 nnn 根柱子中依次放入编号为 111,222,333,…的球“

  1. 每次只能在某根柱子的最上面放球。

  2. 同一根柱子中,任何 222 个相邻球的编号之和为完全平方数。

试设计一个算法,计算出在 nnn 根柱子上最多能放多少个球。例如,在 444 根柱子上最多可放 111111 个球。

对于给定的 nnn,计算在 nnn 根柱子上最多能放多少个球。

思路:
枚举n,拆点,求出小于n能和n组成平方数的数,连边,在残量网络上跑网络流,如果能找到新的增广路代表现有的柱子上还可以放,否则增加柱子,在增广路的过程中记录每个柱子上的数,跟最小路径覆盖那题异曲同工

#include<bits/stdc++.h>
using namespace std;
const int inf =0x3f3f3f3f,maxn=1e5+5,maxm=4e5+5;
int cur[maxn],head[maxn],dep[maxn],to[maxn],flag[maxn],vis[maxn];
int cnt=1,n,m,s=100001,t=100002,now,p;
struct edge{int v,w,nex;
}e[maxm*2];
inline void add_edge(int u,int v,int w){e[++cnt].v=v;e[cnt].w=w;e[cnt].nex=head[u];head[u]=cnt;e[++cnt].v=u;e[cnt].w=0;e[cnt].nex=head[v];head[v]=cnt;
}
bool bfs(){memset(dep,0,sizeof(dep));memcpy(cur,head,sizeof head);dep[s]=1;queue<int>q;q.push(s);while(!q.empty()){int u=q.front();q.pop();for(int i=head[u];i;i=e[i].nex){int v=e[i].v;if(!dep[v]&&e[i].w){dep[v]=dep[u]+1;q.push(v);if(v==t)return true;       }}}return false;
}
int dfs(int u,int now){if(u==t||now==0){return now;}int flow=0,rlow=0;for(int i=cur[u];i;i=e[i].nex){int v=e[i].v;if(e[i].w&&dep[v]==dep[u]+1){if(rlow=dfs(v,min(now,e[i].w))){flow+=rlow;now-=rlow;e[i].w-=rlow;e[i^1].w+=rlow;if(u!=t)to[u>>1]=v>>1;if(now==0)return flow;}}}if(!flow)dep[u]=-1;return flow;
}
int dinic(){int maxflow=0;while(bfs()){maxflow+=dfs(s,inf);}return maxflow;
}
int zz[maxn];
int main(){scanf("%d",&n);int p=0;while(p<=n) {now++;add_edge(s,now*2,1);add_edge(now*2+1,t,1);//     printf("s %d\n",now);//    printf("%d+%d t\n",now,now);for(int i=sqrt(now)+1;i*i<now*2;i++) {add_edge((i*i-now)*2,now*2+1,1);//        printf("%d %d+%d\n",(i*i-now),now,now);}int flow = dinic();if(!flow){zz[++p] = now;}}cout<<now-1<<endl;for(int i=1;i<=n;i++){int u=zz[i];printf("%d",u);u=to[u];while(u&&u!=t>>1){printf(" %d",u);u=to[u];}cout<<endl;}return 0;
}

23.P2764 最小路径覆盖问题

给定有向图 G=(V,E)G=(V,E)G=(V,E) 。设 PPP 是 GGG 的一个简单路(顶点不相交)的集合。如果 VVV 中每个定点恰好在PPP的一条路上,则称 PPP 是 GGG 的一个路径覆盖。PPP中路径可以从 VVV 的任何一个定点开始,长度也是任意的,特别地,可以为 000 。GGG 的最小路径覆盖是 GGG 所含路径条数最少的路径覆盖。设计一个有效算法求一个 GAP (有向无环图) GGG 的最小路径覆盖。

提示:设 V={1,2,...,n}V=\{1,2,...,n\}V={1,2,...,n} ,构造网络 G1={V1,E1}G_1=\{V_1,E_1\}G1​={V1​,E1​} 如下:

V1={x0,x1,...,xn}∪{y0,y1,...,yn}V_1=\{x_0,x_1,...,x_n\}\cup\{y_0,y_1,...,y_n\}V1​={x0​,x1​,...,xn​}∪{y0​,y1​,...,yn​}

E1={(x0,xi):i∈V}∪{(yi,y0):i∈V}∪{(xi,yj):(i,j)∈E}E_1=\{(x_0,x_i):i\in V\}\cup\{(y_i,y_0):i\in V\}\cup\{(x_i,y_j):(i,j)\in E\}E1​={(x0​,xi​):i∈V}∪{(yi​,y0​):i∈V}∪{(xi​,yj​):(i,j)∈E}

每条边的容量均为 111 ,求网络 G1G_1G1​ 的 (x0,y0)(x_0,y_0)(x0​,y0​) 最大流。

题目直接给出了提示,可是我太蒻了没有看懂…

原图是这样子的:

要我们求最小覆盖路径,我们可以先把图看成没有边,此时覆盖路径数为11,通过手玩可以发现,每当我们连上一条边之后,这个路径数就会减少1,而每一条路径上,一个点只能被另外一个点连上,这就符合匹配的原理,我们由此构建出二分图

可以看出最小路径覆盖数=总点数-最大流,因为最大流即为匹配数,被匹配上的点就不需要做出发点

题目要求我们输出路径,在dfs找增广路的时候记录下每一个点u−>vu->vu−>v的vvv就好

#include<bits/stdc++.h>
using namespace std;
const int inf =0x3f3f3f3f,maxn=1e5+5,maxm=4e5+5;
int cur[maxn],head[maxn],dep[maxn],to[maxn],flag[maxn];
int cnt=1,n,m,s,t;
struct edge{int v,w,nex;
}e[maxm*2];
inline void add_edge(int u,int v,int w){e[++cnt].v=v;e[cnt].w=w;e[cnt].nex=head[u];head[u]=cnt;e[++cnt].v=u;e[cnt].w=0;e[cnt].nex=head[v];head[v]=cnt;
}
bool bfs(){memset(dep,0,sizeof(dep));for(int i=0;i<=2*n+5;i++){//如果要拆点或者其他的一定记得把范围开大点!!! cur[i]=head[i];}dep[s]=1;queue<int>q;q.push(s);while(!q.empty()){int u=q.front();q.pop();for(int i=head[u];i;i=e[i].nex){int v=e[i].v;if(!dep[v]&&e[i].w){dep[v]=dep[u]+1;q.push(v);if(v==t)return true;      }}}return false;
}
int dfs(int u,int now){if(u==t||now==0){return now;}int flow=0,rlow=0;for(int i=cur[u];i;i=e[i].nex){int v=e[i].v;if(e[i].w&&dep[v]==dep[u]+1){if(rlow=dfs(v,min(now,e[i].w))){flow+=rlow;now-=rlow;e[i].w-=rlow;e[i^1].w+=rlow;to[u]=v;if(u!=s)flag[v-n]=1;if(now==0)return flow;}}}if(!flow)dep[u]=-1;return flow;
}
int dinic(){int maxflow=0; while(bfs()){maxflow+=dfs(s,inf);}return maxflow;
}
int main(){scanf("%d%d",&n,&m);int u,v,w;s=0,t=2*n+1;for(int i=1;i<=n;i++){add_edge(s,s+i,1);add_edge(s+n+i,t,1);}for(int i=1;i<=m;i++){scanf("%d%d",&u,&v);add_edge(s+u,s+n+v,1);}int ans=dinic();for(int i=1;i<=n;i++){if(!flag[i]){int now=i;printf("%d ",now);while(to[now]&&to[now]!=t){printf("%d ",to[now]-n);now=to[now]-n;}cout<<endl;}}printf("%d",n-ans);return 0;
}

24.P2775 机器人路径规划问题

著名假题,可以用ida*过,咕 咕 咕~

Raki的网络流24题题解总结相关推荐

  1. 解题报告:线性规划与网络流24题

    目录 A.飞行员配对方案问题 (二分图最大匹配)(最大流)[提高+/省选- ] B.太空飞行计划问题(最大权闭合图转最小割.最小割方案输出)[省选/NOI- ] C.最小路径覆盖问题(有向无环图最小路 ...

  2. 【题解】网络流24题一句话题解集合

    最近写了下<线性规划与网络流24题>,发下代码和题解,事实上就是将交给cycycy的题解复制一下 T1 飞行员配对方案问题 solution 裸的匈牙利 code #include< ...

  3. 题解 【网络流24题】太空飞行计划

    [网络流24题]太空飞行计划 Description W 教授正在为国家航天中心计划一系列的太空飞行.每次太空飞行可进行一系列商业性实验而获取利润.现已确定了一个可供选择的实验集合E={E1,E2,- ...

  4. 【线性规划与网络流24题】汽车加油行驶问题 分层图

    汽车加油行驶问题 Time Limit: 1 Sec  Memory Limit: 128 MB Description 给定一个 N*N的方形网格,设其左上角为起点◎,坐标为( 1,1),X轴向右为 ...

  5. 【网络流24题】魔术球问题(最大流)

    [网络流24题]魔术球问题(最大流) 题面 Cogs 题解 是不是像极了最小路径覆盖? 因此,我们枚举放到哪一个球(也可以二分) 然后类似于最小路径覆盖的连边 因为一根柱子对应一个路径的覆盖 所以,提 ...

  6. [ZJOI2010]网络扩容[网络流24题]

    [ZJOI2010]网络扩容[网络流24题] 题意: 给定一张有向图,每条边都有一个容量 c 和一个扩容费用 w.这里扩容费用是指将容量扩大 1 所需的费用.求: 在不扩容的情况下,1 到 n 的最大 ...

  7. P2756 飞行员配对方案问题【网络流24题】

    P2756 飞行员配对方案问题 文章目录 题目背景 题解: 代码: 题目背景 第二次世界大战期间,英国皇家空军从沦陷国征募了大量外籍飞行员.由皇家空军派出的每一架飞机都需要配备在航行技能和语言上能互相 ...

  8. 【网络流24题】餐巾计划问题(最小费用最大流)

    [网络流24题]餐巾计划问题(最小费用最大流) 题面 COGS 洛谷上的数据范围更大,而且要开longlong 题解 餐巾的来源分为两种: ①新买的 ②旧的拿去洗 所以,两种情况分别建图 先考虑第一种 ...

  9. 【网络流24题】星际转移问题(最大流)

    [网络流24题]星际转移问题(最大流) 题面 Cogs 题解 因为天数是未知的,所以我们要想办法处理天数 可以选择二分或者依次累加天数 因为数据范围较小,使用二分可能反而复杂度会增高 所以使用不断累加 ...

  10. 「网络流24题」 题目列表

    「网络流24题」 题目列表 序号 题目标题 模型 题解 1 飞行员配对方案问题 二分图最大匹配 <1> 2 太空飞行计划问题 最大权闭合子图 <2> 3 最小路径覆盖问题 二分 ...

最新文章

  1. Ubuntu 11.1012.04 apt更新错误:Failed to fetch bzip2 packages:...Hash Sum
  2. SAP Spartacus的User明细如何通过ngrx-store-devtools被解析出来
  3. P4590 [TJOI2018]游园会 dp套dp + 状态机
  4. idea ssm打war包_IDEA下从零开始搭建SpringBoot工程
  5. 【英语学习】【WOTD】purview 释义/词源/示例
  6. 中国酸性蒸汽清洗系统市场趋势报告、技术动态创新及市场预测
  7. 华为鸿蒙内测报名要报吗,华为鸿蒙HarmonyOS 2.0公测版/内测版申请和招募指南,快来报名尝鲜吧!...
  8. 2021 互联网公司时薪排行榜出炉!多多排榜首!微软、美团很强!
  9. C语言课程设计:课程管理系统
  10. PyTorch实现ResNet18
  11. 浮动QQ在线客服代码,兼容各大浏览器
  12. Git的下载安装 (图文教程)
  13. vue 高德获取当前经纬度
  14. 新概念51单片机c语言教程考试题,新概念51单片机C语言教程例题.doc
  15. matlab差分法求解ppt,有限差分法求解薛定谔方程_宫建平.pdf
  16. 无线电频谱和波段划分
  17. MacOS基金管理软件
  18. 301转向应该怎么做,冬镜说Apache应该谨记这几点
  19. java fianlly_Java冷知识:finally中的代码一定会执行吗?
  20. 计算机二级office知识框架,计算机二级Office:Excel模拟分析和图表知识点讲解

热门文章

  1. 在MOSS2007中使用收集反馈工作流
  2. FTP已登录,读取目录列表失败
  3. 递增的整数序列链表的插入_leetcode673_go_最长递增子序列的个数
  4. C程序设计--排序(冒泡、选择、插入)--插入
  5. A[1083]List Grade 水题
  6. nyoj107hdu A Famous ICPC Team
  7. python 根据关键字 切割pdf_用python拆分pdf
  8. android 获取录音时长_Android中集成FFmpeg ③执行进度
  9. 【POJ2069HDU3007】模拟退火算法之最小球/圆覆盖
  10. 【2019杭电多校第七场1010=HDU6655】Just Repeat(思维)