额,虽然长的和别人写网络流24题题解的样式很像,但代码都是自己写的,希望能对看官有所帮助QAQ
顺序 | 问题名称 | 问题模型 | 转化模型 |
---|---|---|---|
1 | 飞行员配对方案问题 | 二分图最大匹配 | 网络最大流 |
2 | 太空飞行计划问题 | 最大权闭合图 | 网络最小割 |
3 | 最小路径覆盖问题 | 有向无环图最小路径覆盖 | 网络最大流 |
4 | 魔术球问题 | 有向无环图最小路径覆盖 | 网络最大流 |
5 | 圆桌问题 | 二分图多重匹配 | 网络最大流 |
6 | 最长递增子序列问题 | 最多不相交路径 | 网络最大流 |
7 | 试题库问题 | 二分图多重匹配 | 网络最大流 |
8 | 机器人路径规划问题 | (未解决) | 最小费用最大流 |
9 | 方格取数问题 | 二分图点权最大独立集 | 网络最小割 |
10 | 餐巾计划问题 | 线性规划网络优化 | 最小费用最大流 |
11 | 航空路线问题 | 最长不相交路径 | 最小费用最大流 |
12 | 软件补丁问题 | 最小转移代价 | 最短路径(不是网络流) |
13 | 星际转移问题 | 网络判定 | 网络最大流 |
14 | 孤岛营救问题 | 分层图最短路径 | 最短路径(不是网络流) |
15 | 汽车加油行驶问题 | 分层图最短路径 | 最短路径(不是网络流) |
16 | 数字梯形问题 | 最大权不相交路径 | 最小费用最大流 |
17 | 运输问题 | 网络费用流量 | 最小费用最大流 |
18 | 分配问题 | 二分图最佳匹配 | 最小费用最大流 |
19 | 负载平衡问题 | 最小代价供求 | 最小费用最大流 |
20 | 深海机器人问题 | 线性规划网络优化 | 最小费用最大流 |
21 | 最长k可重区间集问题 | 最大权不相交路径 | 最小费用最大流 |
22 | 最长k可重线段集问题 | 最大权不相交路径 | 最小费用最大流 |
23 | 火星探险问题 | 线性规划网络优化 | 最小费用最大流 |
24 | 骑士共存问题 | 二分图最大独立集 | 网络最小割 |
1、飞行员配对问题
建立一个源点和一个汇点,然后对所有外籍飞行员建一条连接源点的容量为1的边,对所有英国飞行员建一条连接汇点的容量为1的边,跑最大流即可,输出配对方案的时候用ma和mb两个数组记录匹配,反正算法结束的最后一趟一定是最终方案,不停覆盖就行了
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
#define INF 0x3f3f3f3f
const int maxn=250;
struct edge{
int to;//终点
ll cap;//容量
int rev;//反向边
};
int n,m,s,t;
vector<edge> G[maxn];
int level[maxn];//距离标号
int iter[maxn];//当前弧
int ma[maxn];
int mb[maxn];//两个数组维护关系
void add_edge(int from,int to,ll cap){
G[from].push_back((edge){to,cap,G[to].size()});
G[to].push_back((edge){from,0,G[from].size()-1});
}
void bfs(int s){
memset(level,-1,sizeof(level));
queue<int> q;
level[s]=0;
q.push(s);
while(!q.empty())
{
int v=q.front();
q.pop();
for(int i=0;i<G[v].size();i++)
{
edge e=G[v][i];
if(level[e.to]<0&&e.cap>0)
{
level[e.to]=level[v]+1;
q.push(e.to);
}
}
}
}
ll dfs(int v,int t,ll f)
{
if(v==t)
{
return f;
}
for(int &i=iter[v];i<G[v].size();i++)
{
edge &e=G[v][i];
if(e.cap>0&&level[v]<level[e.to])
{
ll d=dfs(e.to,t,min(f,e.cap));
if(d>0)
{
e.cap-=d;
G[e.to][e.rev].cap+=d;
ma[v]=e.to;//每找到增广路,就把v配对上e.to
mb[e.to]=v;//每找到增广路,就把e.to配对上v
return d;
}
}
}
return 0;
}
ll dinic(int s,int t)
{
ll flow=0;
for(;;)
{
bfs(s);
if(level[t]<0) return flow;
memset(iter,0,sizeof(iter));
ll f;
while((f=dfs(s,t,INF))>0){
flow+=f;
}
}
}
int main()
{
cin>>m>>n;
int u,v;
s=n+1;
t=n+2;
while(cin>>u>>v)
{
if(u==-1&&v==-1)
break;
add_edge(u,v,1);
}
for(int i=1;i<=m;i++)
add_edge(s,i,1);
for(int i=m+1;i<=n;i++)
add_edge(i,t,1);
ll ans=dinic(s,t);
cout<<ans<<endl;
for(int i=1;i<=m;i++)
{
if(mb[ma[i]]==i)
{
cout<<i<<" "<<ma[i]<<endl;
}
else if(ma[mb[i]]==i)
{
cout<<i<<" "<<mb[i]<<endl;
}
}
return 0;
}
2、太空飞行计划问题
简化一下题意以后,发现题意是这样的:
给定一张图,有左侧的点和右侧的点,左侧的点点权为正(对应试验),右侧的点点权为负(对应器材),如果选择了左侧的某个点就必须要选右边的一部分点。要求最大化点权和。
如果将左侧的点和右侧的点之间对应连边,实验要求该器材就在两点间连一条边,那么问题就被转化为了这样一个问题:
给定一个有向图,点有点权,选择一个子图,满足子图上如果选择了一个点就必须选择它后继的所有点。最大化点权和。
这是一个经典的网络流问题,如果一个点被选择了则后继必须被选择,那么称该图是 闭合的,因此该问题叫做最大权闭合子图问题。可以使用最小割解决。
具体的建图方法为:
源点向所有正权点连结一条容量为权值的边
保留原图中所有的边,容量为正无穷
所有负权点向汇点连结一条容量为权值绝对值的边
则原图的最大权闭合子图的点权和即为所有正权点权值之和减去建出的网络流图的最小割(最大流)。
(百度有证明)
以下约定源点为 s,汇点为 t。
在最小割图上,如果割掉 s 和 u 之间的边,代表不选择 u 进入子图,如果割掉 v 和 t 之间的边,代表选择 v 进入子图。
跑完最小割(最大流)后,如果点 i 与 s 相连,那么子图上会选择点 i,如果 i 与 t 相连,则不选择点 i即可,相连即是最后一次层次图跑完后,这些点仍在层次图内
简单证明理解如下(简单证明)
显然我们最后一次bfs后,我们的level数组(层次图数组)正好可以用来判断这个条件
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
#define INF 0x7fffffff
const int maxn=250;
struct edge{
int to;//终点
ll cap;//容量
int rev;//反向边
};
int n,m,s,t;
vector<edge> G[maxn];
int level[maxn];//距离标号
int iter[maxn];//当前弧
void add_edge(int from,int to,ll cap){
G[from].push_back((edge){to,cap,G[to].size()});
G[to].push_back((edge){from,0,G[from].size()-1});
}
void bfs(int s){
memset(level,-1,sizeof(level));
queue<int> q;
level[s]=0;
q.push(s);
while(!q.empty())
{
int v=q.front();
q.pop();
for(int i=0;i<G[v].size();i++)
{
edge e=G[v][i];
if(level[e.to]<0&&e.cap>0)
{
level[e.to]=level[v]+1;
q.push(e.to);
}
}
}
}
ll dfs(int v,int t,ll f)
{
if(v==t)
{
return f;
}
for(int &i=iter[v];i<G[v].size();i++)
{
edge &e=G[v][i];
if(e.cap>0&&level[v]<level[e.to])
{
ll d=dfs(e.to,t,min(f,e.cap));
if(d>0)
{
e.cap-=d;
G[e.to][e.rev].cap+=d;
return d;
}
}
}
return 0;
}
ll dinic(int s,int t)
{
ll flow=0;
for(;;)
{
bfs(s);
if(level[t]<0)//最后一次bfs
{
for(int i=1;i<=m;i++)
{
if(level[n+i]>-1)
cout<<i<<" ";
}
cout<<endl;
for(int i=1;i<=n;i++)
{
if(level[i]>-1)
cout<<i<<" ";
}
cout<<endl;
return flow;
}
memset(iter,0,sizeof(iter));
ll f;
while((f=dfs(s,t,INF))>0){
flow+=f;
}
}
}
int main()
{
//freopen("P2762_2.in","r",stdin);
//freopen("sample.out", "w", stdout);
cin>>m>>n;
s=0,t=n+m+1;//s和t要避开会用到的点
int sum=0;
for(int i=1;i<=m;i++)
{
int x;
char c;
scanf("%d",&x);
sum+=x;
add_edge(s,n+i,x);//因为只有n个仪器,所以把n后的点n+i作为实验点,容量为权值
while(1)
{
//这题的输入输出有点垃圾
scanf("%d%c",&x,&c);//每个实验有自己的仪器
add_edge(n+i,x,INF);//把对应的实验点和仪器之间连上无穷大的边
if(c=='\n'||c=='\r')break;
}
}
for(int i=1;i<=n;i++)
{
int x;
cin>>x;
add_edge(i,t,x);//所有负权点向汇点连一条容量为权值的边
}
cout<<sum-dinic(s,t)<<endl;//则原图的最大权闭合子图的点权和即为所有正权点权值之和减去建出的网络流图的最小割
return 0;
}
3、最小路径覆盖问题
简化题意:给你一幅有向无环图,让你找出最少的不相交的路径去覆盖所给的图,问最少要多少条并输出它们。
有一条结论是这样的:
算法:把原图的每个点V拆成Vx和Vy两个点,如果有一条有向边A->B,那么就加边Ax−>By。这样就得到了一个二分图。那么最小路径覆盖=原图的结点数-新图的最大匹配数。
证明:一开始每个点都是独立的为一条路径,总共有n条不相交路径。我们每次在二分图里找一条匹配边就相当于把两条路径合成了一条路径,也就相当于路径数减少了1。所以找到了几条匹配边,路径数就减少了多少。所以有最小路径覆盖=原图的结点数-新图的最大匹配数。
新图是个二分图,二分图的最大匹配数显然就是
源点到左侧点建容量为1的边,右侧点到汇点建容量为1的边,中间边正常建立,然后跑最大流即可
那么我们的最小路径就已经很简单可求出了,那怎么去输出具体的路径呢
我是直接在dfs暴力记录每个节点的后继节点,最后输出即可
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
#define INF 0x3f3f3f3f
const int maxn=500;
struct edge{
int to;//终点
ll cap;//容量
int rev;//反向边
};
int n,m,s,t;
vector<edge> G[maxn];
int level[maxn];
int iter[maxn];
int to[maxn];//记录后继节点
int used[maxn];//标记初始点或后继点
void add_edge(int from,int to,ll cap)
{
G[from].push_back((edge){to,cap,G[to].size()});
G[to].push_back((edge){from,0,G[from].size()-1});
}
void bfs(int s)
{
memset(level,-1,sizeof(level));
queue<int> q;
level[s]=0;
q.push(s);
while(!q.empty())
{
int v=q.front();
q.pop();
for(int i=0;i<G[v].size();i++)
{
edge e=G[v][i];
if(level[e.to]<0&&e.cap>0)
{
level[e.to]=level[v]+1;
q.push(e.to);
}
}
}
}
ll dfs(int v,int t,ll f)
{
if(v==t)
return f;
for(int &i=iter[v];i<G[v].size();i++)
{
edge &e=G[v][i];
if(e.cap>0&&level[v]<level[e.to])
{
ll d=dfs(e.to,t,min(f,e.cap));
if(d>0)
{
to[v]=e.to;//记录每个点后面到哪个点
if(v!=s&&e.to>n) used[e.to-n]=1;
//used[后继点]为1,初始点没有放进来,所以最后所有后继点为1,初始点为0
e.cap-=d;
G[e.to][e.rev].cap+=d;
return d;
}
}
}
return 0;
}
ll dinic(int s,int t)
{
ll flow=0;
for(;;)
{
bfs(s);
if(level[t]<0)
{
for(int i=1;i<=n;i++)
{
if(!used[i])//used[i]为0说明是初始点,直接输出
{
cout<<i;
int now=i;
while(to[now]&&to[now]!=t)//从这个初始点一直沿着记录走即可
{
cout<<" "<<to[now]-n;//减n这些是因为直接建立二分图的时候决定的
//A连B就是左侧A连右侧B,而右侧B我取的标号为n+i
now=to[now]-n;
}
cout<<endl;
}
}
return flow;
}
memset(iter,0,sizeof(iter));
ll f;
while((f=dfs(s,t,INF))>0){
flow+=f;
}
}
}
int main()
{
cin>>n>>m;
s=0;t=2*n+1;
int u,v;
while(m--)
{
cin>>u>>v;
add_edge(u,n+v,1);
}
for(int i=1;i<=n;i++)
{
add_edge(s,i,1);
add_edge(i+n,t,1);
}
ll ans=dinic(s,t);
cout<<n-ans<<endl;
return 0;
}
4、魔术球问题
简化题意:给你n个柱子,最多能放几个球 等价于 有1-m(不确定)个球,求最少能用几个柱子装下他们
每个球要求:1、和相邻的球的和是完全平方数 2、自立门户
那么我们显然可以用和上一题类似的思路去解决
我们枚举球的个数
不停将球放进图里,按照要求跑一个最小路径覆盖,那么只要我们现在的球数m的最小路径覆盖小于n,就让球++,直到m的最小路径覆盖第一次为n-1时退出循环,那此时m-1即是我们最大能放的球数
那么怎么建图呢
我们将每个球拆成两个点:第i个球入点为2*i,出点为2*i+1,这么设置有一个好处,就是最后输出方案的时候,不管是出点还是入点都只要除2就是球的标号本身,比设入点为2*i-1 出点为2*i要好
然后找1到 i 的范围,满足要求 1,就在图中加(j * 2,i * 2+1)的边(j是当前找到的那个数)
最后输出方案的话和上题一样思路
代码如下,可以对照上题看看改变的地方
#include<bits/stdc++.h>
using namespace std;
const int maxn=100050;//开大点,我也不知道开多少能a,反正开到现在这么大a了
typedef long long ll;
#define inf 0x3f3f3f3f
struct edge{
int to;
ll cap;
int rev;
};
vector<edge> g[maxn];
int level[maxn];
int iter[maxn];
int p[maxn];
int to[maxn];//记录后继节点
int used[maxn];//标记初始点或后继点
int s,t,n;
void add_edge(int from,int to,ll cap)
{
g[from].push_back((edge){to,cap,g[to].size()});
g[to].push_back((edge){from,0,g[from].size()-1});
}
void bfs(int s)
{
memset(level,-1,sizeof(level));
level[s]=0;
queue<int> q;
q.push(s);
while(!q.empty())
{
int v=q.front();
q.pop();
for(int i=0;i<g[v].size();i++)
{
edge e=g[v][i];
if(level[e.to]<0&&e.cap>0)
{
q.push(e.to);
level[e.to]=level[v]+1;
}
}
}
}
ll dfs(int v,int t,ll flow)
{
if(v==t)
return flow;
for(int &i=iter[v];i<g[v].size();i++)
{
edge &e=g[v][i];
if(e.cap>0&&level[v]+1==level[e.to])
{
ll d=dfs(e.to,t,min(flow,e.cap));
if(d>0)
{
to[v/2]=e.to/2;//记录后继结点
if(v!=s) used[e.to/2]=1;//记录非起始结点
e.cap-=d;
g[e.to][e.rev].cap+=d;
return d;
}
}
}
return 0;
}
ll dinic(int s,int t)
{
ll ans=0;
for(;;)
{
bfs(s);
if(level[t]<0)
{
return ans;
}
memset(iter,0,sizeof(iter));
ll d;
while((d=dfs(s,t,inf))>0)
ans+=d;
}
}
int main()
{
cin>>n;
s=0,t=100005;
int pillar=0;
int ans=0;https://blog.csdn.net/weixin_43945141/article/details/96117750
while(pillar<=n)
{
ans++;
add_edge(s,ans*2,1);
add_edge(ans*2+1,t,1);
for(int j=1;j<ans;j++)
{
int x=sqrt(j+ans);
if(x*x==j+ans)
add_edge(j*2,ans*2+1,1);//满足要求1就加一条边
}
int d=dinic(s,t);
if(d==0)//如果当前的图找不到增广路,就意味着要新开一根柱子
{
pillar++;
p[pillar]=ans;
}
}
cout<<ans-1<<endl;
for(int i=1;i<ans;i++)
{
if(used[i]==0)
{
cout<<i;
int now=i;
while(to[now]&&to[now]!=t/2)
{
cout<<" "<<to[now];
now=to[now];
}
cout<<endl;
}
}
return 0;
}
5、圆桌问题
额,感觉这题比前面四题都简单,像个模板题,我们可以类比第一题的过程
建立一张二分图,二分图的左边是单位,右边是桌子
由于我们的特殊限制 每个单位只能在一个桌子坐一个人
所以我们就把每个单位向各个桌子连一道流量为1的边
由源点向每个单位连上单位人数的边,由每个圆桌向汇点连上圆桌人数的边
然后跑一下最大匹配 如果最大匹配数等于所有单位的人数和
那么就可以 完全安排 否则不能完全安排
这个题目的统计答案要比前两个好弄很多,我们只要最后遍历整幅图,只要这幅图中单位到桌子的边的容量不为1,就代表这条边被选上了
那么我们暴力遍历然后输出即可
#include<bits/stdc++.h>
using namespace std;
const int maxn=500;
typedef long long ll;
#define inf 0x3f3f3f3f
struct edge{
int to;
ll cap;
int rev;
};
vector<edge> g[maxn];
int level[maxn];
int iter[maxn];
int p[maxn];
int s,t,n,m;
void add_edge(int from,int to,ll cap)
{
g[from].push_back((edge){to,cap,g[to].size()});
g[to].push_back((edge){from,0,g[from].size()-1});
}
void bfs(int s)
{
memset(level,-1,sizeof(level));
level[s]=0;
queue<int> q;
q.push(s);
while(!q.empty())
{
int v=q.front();
q.pop();
for(int i=0;i<g[v].size();i++)
{
edge e=g[v][i];
if(level[e.to]<0&&e.cap>0)
{
q.push(e.to);
level[e.to]=level[v]+1;
}
}
}
}
ll dfs(int v,int t,ll flow)
{
if(v==t)
return flow;
for(int &i=iter[v];i<g[v].size();i++)
{
edge &e=g[v][i];
if(e.cap>0&&level[v]+1==level[e.to])
{
ll d=dfs(e.to,t,min(flow,e.cap));
if(d>0)
{
e.cap-=d;
g[e.to][e.rev].cap+=d;
return d;
}
}
}
return 0;
}
ll dinic(int s,int t)
{
ll ans=0;
for(;;)
{
bfs(s);
if(level[t]<0)
{
return ans;
}
memset(iter,0,sizeof(iter));
ll d;
while((d=dfs(s,t,inf))>0)
ans+=d;
}
}
int main()
{
cin>>m>>n;
int r,c;
s=0,t=n+m+1;
ll sum=0;
for(int i=1;i<=m;i++)
{
cin>>r;
sum+=r;
add_edge(s,i,r);
}
for(int i=1;i<=n;i++)
{
cin>>c;
add_edge(m+i,t,c);
}
for(int i=1;i<=m;i++)
{
for(int j=1;j<=n;j++)
{
add_edge(i,m+j,1);
}
}
if(dinic(s,t)==sum)
{
cout<<1<<endl;
for(int i=1;i<=m;i++)
{
for(int j=0;j<g[i].size();j++)
{
if(g[i][j].cap==0)
cout<<g[i][j].to-m<<" ";
}
cout<<endl;
}
}
else
cout<<0<<endl;
return 0;
}
6、最长递增子序列问题
这题题意很简单
第一问:求LIS,用DP解决即可
第二问:我们类比一下前几题的过程,考虑建一幅怎样的图可以跑一个网络流使结果和我们所要求的的最多的不下降子序列相关联
那么我们很容易想到,如果我们建的图中一条路径正好能代表选出的一个长度为s的序列,那么我们只要看最多有几条不相交的路径即可
建图方法:我们设立源点s=0,汇点t=2*n+1,把每个序列点拆成i和n+i两个点代表入点和出点
1、如果dp[i]=1,那么连接s和i,容量为1
2、如果dp[i]=n,那么连接n+i和t,容量为1
3、连接i和i+n,容量为1
4、如果对于j<i,有a[j]<=a[i]并且dp[j]+1==dp[i],那么我们连接j+n和i,容量为1,代表从j接下去走到i就是a[j]后面可以选用a[i]
显然,我们只要把容量设为1的话,那么就满足了原题只能取一次的限制,因为你取用过的路径上的数容量变为0,肯定不能走第二次了,那么我们只要在这个图上跑一个最大流,即是答案,最大流即为增广路条数即为最多不相交路径数即为最多的长度为s的不下降子序列
第三问:我们只要把第二问中关于x1和xn的容量限制解除即可(重新建图当然可以),我们可以直接添加
1、s到1,容量无穷的边
2、1到n+1,n到n+n,容量为无穷
3、如果dp[n]=s,那么添加n+n到t,容量为无穷
再跑一遍最大流,加上原来的结果即是答案
注意特判n=1的点,因为按照原来的建图,在第三问你会添加s到1,入点1到出点1,出点1到汇点t均容量为无穷的路径,这种图跑出的结果显然容量为无穷,是不合理的
#include<bits/stdc++.h>
using namespace std;
const int maxn=2500;
typedef long long ll;
#define inf 0x7f7f7f7f
struct edge{
int to;
ll cap;
int rev;
};
vector<edge> g[maxn];
int level[maxn];
int iter[maxn];
int s,t,n,len;
int a[maxn];
int dp[maxn];
void add_edge(int from,int to,ll cap)
{
g[from].push_back((edge){to,cap,g[to].size()});
g[to].push_back((edge){from,0,g[from].size()-1});
}
void bfs(int s)
{
memset(level,-1,sizeof(level));
level[s]=0;
queue<int> q;
q.push(s);
while(!q.empty())
{
int v=q.front();
q.pop();
for(int i=0;i<g[v].size();i++)
{
edge e=g[v][i];
if(level[e.to]<0&&e.cap>0)
{
q.push(e.to);
level[e.to]=level[v]+1;
}
}
}
}
ll dfs(int v,int t,ll flow)
{
if(v==t)
return flow;
for(int &i=iter[v];i<g[v].size();i++)
{
edge &e=g[v][i];
if(e.cap>0&&level[v]+1==level[e.to])
{
ll d=dfs(e.to,t,min(flow,e.cap));
if(d>0)
{
e.cap-=d;
g[e.to][e.rev].cap+=d;
return d;
}
}
}
return 0;
}
ll dinic(int s,int t)
{
ll ans=0;
for(;;)
{
bfs(s);
if(level[t]<0)
{
return ans;
}
memset(iter,0,sizeof(iter));
ll d;
while((d=dfs(s,t,inf))>0)
ans+=d;
}
}
int main()
{
cin>>n;
s=0,t=n*2+1;
for(int i=1;i<=n;i++)
cin>>a[i];
if(n==1)
{
cout<<1<<endl;
cout<<1<<endl;
cout<<1<<endl;
return 0;
}
//dp
int len=0;
for(int i=1;i<=n;i++)
{
dp[i]=1;
for(int j=1;j<i;j++)
{
if(a[j]<=a[i])
dp[i]=max(dp[i],dp[j]+1);
}
len=max(len,dp[i]);
}
cout<<len<<endl;
//2
for(int i=1;i<=n;i++)
{
if(dp[i]==1)
add_edge(s,i,1);
if(dp[i]==len)
add_edge(i+n,t,1);
add_edge(i,i+n,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 ans=dinic(s,t);
cout<<ans<<endl;
//3
add_edge(s,1,inf-1);
add_edge(n,n+n,inf-1);
add_edge(1,n+1,inf-1);
if(dp[n]==len)
add_edge(n+n,t,inf-1);
ans+=dinic(s,t);
cout<<ans<<endl;
return 0;
}
7、试题库问题
题意:
给出k代表有k种类型的题目,n代表总共n到题
接下来一行,每行给出第i种类型题目要求的题目数
接下来n行,每行第一个数p代表第i到题符合几种类型,然后给出p个数代表这题符合哪几种类型
问你能不能找到一个满足要求的题目方案
这题基本和圆桌问题一样
我们考虑相同的建图过程
1、对于试题类型1到k,我们连接s到类型i,容量为该类型要求的试题数ki
2、连接每个类型下有什么题目,容量为1
3、把每个题目连向汇点,容量为1
跑一遍最大流,最大流不为0就代表有结果,从类型1到k,遍历所有指向题目的边,只要容量为0就代表选上了,输出即可
#include<bits/stdc++.h>
using namespace std;
const int maxn=2500;
typedef long long ll;
#define inf 0x3f3f3f3f
struct edge{
int to;
ll cap;
int rev;
};
vector<edge> g[maxn];
int level[maxn];
int iter[maxn];
int s,t,k,n,m;
void add_edge(int from,int to,ll cap)
{
g[from].push_back((edge){to,cap,g[to].size()});
g[to].push_back((edge){from,0,g[from].size()-1});
}
void bfs(int s)
{
memset(level,-1,sizeof(level));
queue<int> q;
level[s]=0;
q.push(s);
while(!q.empty())
{
int v=q.front();
q.pop();
for(int i=0;i<g[v].size();i++)
{
edge e=g[v][i];
if(level[e.to]<0&&e.cap>0)
{
q.push(e.to);
level[e.to]=level[v]+1;
}
}
}
}
ll dfs(int v,int t,ll flow)
{
if(v==t)
return flow;
for(int &i=iter[v];i<g[v].size();i++)
{
edge &e=g[v][i];
if(level[e.to]==level[v]+1&&e.cap>0)
{
ll d=dfs(e.to,t,min(flow,e.cap));
if(d>0)
{
e.cap-=d;
g[e.to][e.rev].cap+=d;
return d;
}
}
}
return 0;
}
ll dinic(int s,int t)
{
ll ans=0;
for(;;)
{
bfs(s);
if(level[t]<0)
{
return ans;
}
memset(iter,0,sizeof(iter));
ll d;
while((d=dfs(s,t,inf))>0)
{
ans+=d;
}
}
}
int main()
{
cin>>k>>n;
s=0,t=n+k+1;
int tnt;
for(int i=1;i<=k;i++)
{
cin>>tnt;
add_edge(s,i,tnt);
}
for(int i=1;i<=n;i++)
{
add_edge(k+i,t,1);
int p,type;
cin>>p;
while(p--)
{
cin>>type;
add_edge(type,k+i,1);
}
}
ll ans=dinic(s,t);
if(ans==0)
cout<<"No Solution!"<<endl;
else
{
for(int i=1;i<=k;i++)
{
cout<<i<<":";
for(int j=0;j<g[i].size();j++)
{
if(g[i][j].cap==0&&g[i][j].to!=s)
cout<<" "<<g[i][j].to-k;
}
cout<<endl;
}
}
return 0;
}
8、机器人路径规划问题
额,听说是一道假题,跳了
9、方格取数问题
走远了,这图一开始看着是个二分图,想着按前面几题的思维做,然后把所有点拆成入点和出点,每个出点连接他影响不到的点,但后面发现这样的图跑的最大流显然是不对的
然后想到可以假设一开始取所有的点,然后把每个点连接他影响的到的点,那么跑一个最小割(最大流),就能求出最大和=全局和-最小舍弃和
建图:假设把原图按照(i+j)%2=0和(i+j)%2=1分成黑点和白点,显然黑点和白点是相邻关系
然后连接源点到黑点,容量为点权
连接白点到汇点,容量为点权
连接黑点到每个他能影响到的白点,容量为inf
这样跑一个最小割(最大流)即可
PS:或许有人会觉得能用贪心做,黑点和与白点和取最大值,这种显然是错误的,考虑如下数据即可
9 1 1
1 1 5
1 5 1
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=15000;
#define inf 0x3f3f3f3f
struct edge{
int to;
ll cap;
int rev;
};
vector<edge> g[maxn];
int level[maxn];
int iter[maxn];
int n,m,s,t;
int dx[4]={1,-1,0,0},dy[4]={0,0,1,-1};
void add_edge(int from,int to,ll cap)
{
g[from].push_back((edge){to,cap,g[to].size()});
g[to].push_back((edge){from,0,g[from].size()-1});
}
void bfs(int s)
{
memset(level,-1,sizeof(level));
queue<int> q;
level[s]=0;
q.push(s);
while(!q.empty())
{
int v=q.front();q.pop();
for(int i=0;i<g[v].size();i++)
{
edge e=g[v][i];
if(level[e.to]<0&&e.cap>0)
{
level[e.to]=level[v]+1;
q.push(e.to);
}
}
}
}
ll dfs(int v,int t,ll f)
{
if(v==t)
return f;
for(int &i=iter[v];i<g[v].size();i++)
{
edge &e=g[v][i];
if(e.cap>0&&level[e.to]==level[v]+1)
{
ll d=dfs(e.to,t,min(f,e.cap));
if(d>0)
{
e.cap-=d;
g[e.to][e.rev].cap+=d;
return d;
}
}
}
return 0;
}
ll dinic(int s,int t)
{
ll ans=0;
for(;;)
{
bfs(s);
if(level[t]<0)
return ans;
memset(iter,0,sizeof(iter));
ll d;
while((d=dfs(s,t,inf))>0)
ans+=d;
}
}
int main()
{
cin>>m>>n;
s=0;t=n*m+5;
ll sum=0;
for(int i=1;i<=m;i++)
{
for(int j=1;j<=n;j++)
{
int x;
cin>>x;
sum+=x;
int id=(i-1)*n+j;
if((i+j)%2==0)
add_edge(s,id,x);
else
add_edge(id,t,x);
}
}
for(int i=1;i<=m;i++)
{
for(int j=1;j<=n;j++)
{
if((i+j)%2==0)
{
for(int k=0;k<4;k++)
{
int x=i+dx[k];
int y=j+dy[k];
if(x<=m&&x>0&&y<=n&&y>0)
{
int id1=(i-1)*n+j;
int id2=(x-1)*n+y;
add_edge(id1,id2,inf);
}
}
}
}
}
ll ans=dinic(s,t);
cout<<sum-ans<<endl;
return 0;
}
10、餐巾计划问题
最小费用最大流的blog还没写,以后可能会补上,大致思想就是每次依据费用跑最(小)短路,然后沿着这条路把流量拉满,不停增广的样子
回到这题
建图的话多画画就出来了
把每天分成两个结点(晚上和早上)
1、s向晚上连容量为当天所需毛巾量的边,费用为0,代表当天晚上会收到这么多脏毛巾
2、早上向t连容量为当天所需毛巾量的边,费用为0,代表一天要用掉这么多毛巾
3、每天晚上向第二天晚上连边,费用为0,容量为inf,代表第一天的脏毛巾可以留到第二天
4、s向早上连容量为inf的边,费用为p,代表每天早上都可以随便买毛巾
5、每天晚上向自己能影响到的早上连边,容量为inf,费用为快洗或慢洗所需的费用,代表一天晚上的脏毛巾可以送去洗直到那一天早上去用
#include <bits/stdc++.h>
using namespace std;
#define inf 0x3f3f3f3f
typedef long long ll;
typedef pair<int,int> P;//first保存最短距离,second保存顶点编号
const int maxn=4500;
struct edge
{
int to;
ll cap;
ll cost;
int rev;
};
vector<edge> g[maxn];//图的邻接表表示
int h[maxn];//顶点的势
int dist[maxn];//最短距离
int prevv[maxn],preve[maxn];//最短路中的前驱节点和对应的边
int n;//顶点数
int s,t;
ll maxflow=0,mincost=0;
ll p,fast_day,fast_price,slow_day,slow_price;
void add_edge(int from, int to, ll cap, ll cost)
{
g[from].push_back((edge){to,cap,cost,g[to].size()});
g[to].push_back((edge){from,0,-cost,g[from].size()-1});
}
//求解从s到t流量为f的最小费用流
//如果没有流量为f的流,则返回-1
//Dijkstra算法
int min_cost_flow(int s,int t,int f)
{
memset(h,0,sizeof(h));
while(f>0){
//使用dijkstra算法更新h
priority_queue<P,vector<P>,greater<P> > que;
memset(dist,inf,sizeof(dist));
dist[s]=0;
que.push(P(0,s));
while(!que.empty())
{
P now=que.top();que.pop();
if(dist[now.second]<now.first) continue;
int v=now.second;
for(int i=0;i<g[v].size();i++)
{
edge &e=g[v][i];
if(e.cap>0&&dist[e.to]>dist[v]+e.cost+h[v]-h[e.to])
{
dist[e.to]=dist[v]+e.cost+h[v]-h[e.to];
prevv[e.to]=v;
preve[e.to]=i;
que.push(P(dist[e.to],e.to));
}
}
}
if(dist[t]==inf)//不能再增广
{
break;
}
for(int i=0;i<=2*n+1+1;i++) h[i]+=dist[i];
//沿s到t的最短路尽量推广
ll d=f;
for(int v=t;v!=s;v=prevv[v])
{
d=min(d,g[prevv[v]][preve[v]].cap);
}
f-=d;
maxflow+=d;
mincost+=d*h[t];
for(int v=t;v!=s;v=prevv[v])
{
edge &e=g[prevv[v]][preve[v]];
e.cap-=d;
g[v][e.rev].cap+=d;
}
}
return 0;
}
int main()
{
cin>>n;
s=0;t=n*2+1;
//源点向晚上连边,早上向汇点连边
for(int i=1;i<=n;i++)
{
ll r;
cin>>r;
add_edge(s,i,r,0);
add_edge(i+n,t,r,0);
}
cin>>p>>fast_day>>fast_price>>slow_day>>slow_price;
//向第二天晚上连边
for(int i=1;i<=n-1;i++)
{
add_edge(i,i+1,inf,0);
}
//源点向早上连边,代表买毛巾
for(int i=1;i<=n;i++)
{
add_edge(s,i+n,inf,p);
}
//快洗和慢洗,注意判断边界
for(int i=1;i<=n;i++)
{
if(i+slow_day<=n)
add_edge(i,i+slow_day+n,inf,slow_price);
if(i+fast_day<=n)
add_edge(i,i+fast_day+n,inf,fast_price);
}
min_cost_flow(s,t,inf);
cout<<mincost<<endl;
return 0;
}
11、航空路线问题
这题我们可以模仿第六题的建图过程,在第六题里我们也是限制了每个元素只能用一次,这里限制了每个点只能用一次,跑出最长的长度。我们只要把边的费用设为负数,那么跑出来的最’小’费用最大流一定会尽可能多的去走边(事实上把费用设为负数就是最大费用最大流),那就是最长长度,跑一次最大流即为答案。注意,我们从西到东再到西,可以看成找两条不相交的从东到西的路径且最长
建图:
把每个点拆为两个点i和i+n
1、连接i和i+n容量为1,费用为-1,代表每个点只能用一次,走过一点最小费用就会小1,注意特判1和n+1,n和n+n,容量为2,起点终点能用2次
2、对于边u和v,(u<v),连接u+n和v,容量为1,费用为0
3、连接源点到1,容量为2,费用为0
4、连接2*n到汇点,容量为2,费用为0
然后跑一次最小费用最大流,得出来的最小费用(一定是负数,因为负权边肯定会多走)取反即为最大费用最大流
因为我们要找两条不相交路径,如果存在,最大流一定是2,如果最大流是1的话我们特判一下之前有没有说过有1到n的直通路径,有的话也是可接受的,其他情况就是no solution了
这题难点在于输出路径,卡了我两天多,可太难辣QAQ
#include <bits/stdc++.h>
using namespace std;
#define inf 0x3f3f3f3f
typedef long long ll;
typedef pair<int,int> P;//first保存最短距离,second保存顶点编号
const int maxn=5050;
struct edge
{
int to;
ll cap;
ll cost;
int rev;
};
vector<edge> g[maxn];//图的邻接表表示
int h[maxn];//顶点的势
int dist[maxn];//最短距离
int prevv[maxn],preve[maxn];//最短路中的前驱节点和对应的边
int n,m,s,t;
ll maxflow,mincost;
int vis[300]={0};
map<string,int> ma;
string word[300];
void add_edge(int from, int to, int cap, int cost)
{
g[from].push_back((edge){to,cap,cost,g[to].size()});
g[to].push_back((edge){from,0,-cost,g[from].size()-1});
}
//求解从s到t流量为f的最小费用流
//如果没有流量为f的流,则返回-1
//Dijkstra算法
int min_cost_flow(int s,int t,int f)
{
memset(h,0,sizeof(h));
while(f>0){
//使用dijkstra算法更新h
priority_queue<P,vector<P>,greater<P> > que;
memset(dist,inf,sizeof(dist));
dist[s]=0;
que.push(P(0,s));
while(!que.empty())
{
P now=que.top();que.pop();
if(dist[now.second]<now.first) continue;
int v=now.second;
for(int i=0;i<g[v].size();i++)
{
edge &e=g[v][i];
if(e.cap>0&&dist[e.to]>dist[v]+e.cost+h[v]-h[e.to])
{
dist[e.to]=dist[v]+e.cost+h[v]-h[e.to];
prevv[e.to]=v;
preve[e.to]=i;
que.push(P(dist[e.to],e.to));
}
}
}
if(dist[t]==inf)//不能再增广
{
break;
}
for(int i=0;i<=2*n+1;i++) h[i]+=dist[i];
//沿s到t的最短路尽量推广
ll d=f;
for(int v=t;v!=s;v=prevv[v])
{
d=min(d,g[prevv[v]][preve[v]].cap);
}
f-=d;
maxflow+=d;
mincost+=d*h[t];
for(int v=t;v!=s;v=prevv[v])
{
edge &e=g[prevv[v]][preve[v]];
e.cap-=d;
g[v][e.rev].cap+=d;
}
}
return 0;
}
vector<int> path;
void print(int x){
int now = x;
if(x<n+1)now+=n;
path.push_back(now);
vis[now] = 1;
for(int i=0;i<g[now].size();i++){
edge e = g[now][i];
if(e.to==2*n+1||e.to==0)continue;
if(e.to<n+1)e.to+=n;
if(e.cap==0&&!vis[e.to])print(e.to);
}
}
int main()
{
ios::sync_with_stdio(false);
cin>>n>>m;
s=0;t=2*n+1;
for(int i=1;i<=n;i++)
{
cin>>word[i];
ma[word[i]]=i;
}
bool flag=false;
for(int i=1;i<=m;i++)
{
string a,b;
cin>>a>>b;
int id1=ma[a];
int id2=ma[b];
if(id1>id2) swap(id1,id2);//让西边向东边连边
if(id1==1&&id2==n)//起点终点能够直连
flag=true;
add_edge(id1+n,id2,1,0);
}
for(int i=1;i<=n;i++)
{
if(i==1||i==n)
add_edge(i,i+n,2,-1);
else
add_edge(i,i+n,1,-1);
}
add_edge(s,1,2,0);
add_edge(2*n,t,2,0);
min_cost_flow(s,t,inf);
if(maxflow==2)
{
mincost=-mincost;
mincost-=2;
cout<<mincost<<endl;
print(1);
int len = 0;
for(int i=0;i<path.size();i++){
if(path[i]!=2*n&&path[i]!=0&&path[i]!=n)cout<<word[path[i]-n]<<endl;
else if(path[i]==n||path[i]==2*n){
cout<<word[n]<<endl;
len = i;
break;
}
}
for(int i=path.size()-1;i>len;i--){
cout<<word[path[i]-n]<<endl;
}
cout<<word[1]<<endl;
}
else if(maxflow==1&&flag==true)
{
cout<<2<<endl;
cout<<word[1]<<endl;
cout<<word[n]<<endl;
cout<<word[1]<<endl;
return 0;
}
else
{
cout<<"No Solution!"<<endl;
return 0;
}
return 0;
}
12、软件补丁问题
这题不是网络流喔
看下来是个状态压缩+最短路的题目
按照洛谷的题解思路大概是:
因为最多只有20个补丁,所以我们可以开一个1<<20的数组来记录每个病毒的状态
一开始有n个病毒,也就是起始点是(1<<n)-1,目标点就是0,也就是一个病毒都没有
我们记录每个补丁的b1,b2,f1,f2,
如果这个补丁可以修复,那就b1[]和当前状态每一位&1之后是b1,和b2[] & 之后==0
然后产生的影响就是f1[]中的位=0,f2[]中的位=1
使用了一个补丁之后将f1位置上变成0,f2位置上变成1。我们还是按照之前的思路,将f1,f2也计算成两个状态。如果需要将f2位置变成1,那么只需要用当前状态或上f2。如果需要将f1位置变成0,我们可以或上一个f1,使得当前状态的所有f1位置都变成1,再异或一个f1,这样就可以将f1所有位置变成0.
int y=((x|p[i].f1)|p[i].f2)^p[i].f1;
#include<cstdio>
#include<queue>
#include<cstring>
using namespace std;
struct pack{int f1,f2,b1,b2,t;}p[505];
int n,m,fir,minn[1<<22],tag; //由于最多有2^20种状态,数组要开够
queue<int>q;bool exi[1<<22];
inline void read(int &x){
char ch=getchar();
while(ch<'0'||ch>'9')ch=getchar();
x=ch^48;ch=getchar();
while(ch>='0'&&ch<='9'){
x=(x<<3)+(x<<1)+(ch^48);
ch=getchar();
}
}
inline int gtag(){ //手写读入可以防止出错
char ch=getchar();
while(ch!='+'&&ch!='-'&&ch!='0')ch=getchar();
if(ch=='+')return 1;
if(ch=='-')return 2;
return 0;
}
void spfa(){ //关于spfa,它还没死
memset(minn,0x7f,sizeof(minn));
minn[fir]=0;
q.push(fir);
while(!q.empty()){
int x=q.front();
for(int i=1;i<=m;++i) //枚举每一个包
if((x&p[i].b1)==p[i].b1&&(x&p[i].b2)==0){ //判断先决
int y=((x|p[i].f1)|p[i].f2)^p[i].f1;//得到运行后状态
if(minn[x]+p[i].t<minn[y]){
minn[y]=minn[x]+p[i].t;
if(!exi[y]){
q.push(y);
exi[y]=true;
}
}
}
exi[x]=false;
q.pop();
}
}
int main(){
read(n);read(m);
for(int i=1;i<=m;++i){
read(p[i].t);
for(int j=1;j<=n;++j){
tag=gtag();
if(tag==1)p[i].b1|=(1<<j-1);
if(tag==2)p[i].b2|=(1<<j-1); //得到每个补丁包的先决条件串
}
for(int j=1;j<=n;++j){
tag=gtag();
if(tag==1)p[i].f2|=(1<<j-1);
if(tag==2)p[i].f1|=(1<<j-1); //得到每个补丁包运行之后的状态串
}
}
fir=(1<<n)-1; //得到初始状态
spfa();
if(minn[0]==minn[(1<<22)-1])printf("0"); //如果根本到达不了目标状态,就是无解
else printf("%d",minn[0]);
return 0;
}
13、星际转移问题
这题虽然是跑最大流,但和以前的最大流不是太一样
他是一个按时间分层的图,然后在这个图上跑最大流
对于一开始判断能不能到达可以用并查集,反正能连通,哪怕再久都有答案,不能连通直接输出0
然后考虑枚举时间
所有的点为“第i个星际站在第t秒”这样一个状态的点,那么枚举的答案每增加1,就需要新建“一套”地球和太空站的点。
源点向每一个“地球”连一条容量为inf的边,每个空间站向下一时间的该空间站连一条容量为inf的边,代表时间间的转移。
每个飞船现在在哪个星球,下一秒会飞到哪一个星球都可以计算得到,所以直接连边,容量为飞船载人量。
月球就是汇点。
然后跑最大流,如果最大流大于需要转移的人数了,那么就得到了解。
具体看代码吧
#include<bits/stdc++.h>
using namespace std;
const int maxn=1500;//太小re了
const int maxnode=100050;//不用太大,太大mle了
typedef long long ll;
#define inf 0x3f3f3f3f
struct edge{
int to;
ll cap;
int rev;
};
vector<edge> g[maxnode];
int level[maxnode];
int iter[maxnode];
int pre[maxn];//并查集的father数组
int ship[maxn][maxn];//飞船关系ship[i][j]表示第i个飞船j秒钟在哪个太空站
int people[maxn],num[maxn];//飞船人数与站点数
int n,m,k,s,t;
ll ans,nowpeople=0;
void makeSet(int n) {
for(int i = 1;i <= n;i++) pre[i] = i;
}
int find(int x) {
if (x != pre[x]) pre[x] = find(pre[x]);
return pre[x];
}
void join(int root1, int root2) //判断是否连通,不连通就合并
{
int x, y;
x = find(root1);
y = find(root2);
if(x != y) //如果不连通,就把它们所在的连通分支合并
pre[x] = y;
}
void add_edge(int from,int to,ll cap)
{
g[from].push_back((edge){to,cap,g[to].size()});
g[to].push_back((edge){from,0,g[from].size()-1});
}
void bfs(int s)
{
memset(level,-1,sizeof(level));
queue<int> q;
q.push(s);
level[s]=0;
while(!q.empty())
{
int v=q.front();q.pop();
for(int i=0;i<g[v].size();i++)
{
edge e=g[v][i];
if(level[e.to]<0&&e.cap>0)
{
level[e.to]=level[v]+1;
q.push(e.to);
}
}
}
}
ll dfs(int v,int t,ll f)
{
if(v==t)
return f;
for(int &i=iter[v];i<g[v].size();i++)
{
edge &e=g[v][i];
if(level[e.to]>level[v]&&e.cap>0)
{
ll d=dfs(e.to,t,min(e.cap,f));
if(d>0)
{
e.cap-=d;
g[e.to][e.rev].cap+=d;
return d;
}
}
}
return 0;
}
ll dinic(int s,int t)
{
ll flow=0;
for(;;)
{
bfs(s);
if(level[t]<0)
{
return flow;
}
memset(iter,0,sizeof(iter));
ll d;
while((d=dfs(s,t,inf))>0)
{
flow+=d;
}
}
}
int main()
{
cin>>n>>m>>k;
makeSet(n+5);
s=0;
t=maxnode-10;
for(int i=1;i<=m;i++)
{
cin>>people[i]>>num[i];
for(int j=0;j<num[i];j++)
{
cin>>ship[i][j];
if(ship[i][j]==0) ship[i][j]=n+1;//如果起点在地球,设标号为n+1
if(ship[i][j]==-1) ship[i][j]=n+2;//如果起点在月亮,设标号为n+2
if(j!=0) join(ship[i][j-1],ship[i][j]);//把上一秒的位置和这一秒的位置连起来
}
}
if(find(n+1)!=find(n+2))//既然站点间没办法联通,那就无解
{
cout<<0<<endl;
return 0;
}
for(ans=1;;ans++)//去枚举答案
{
add_edge(s,(ans-1)*(n+1)+n+1,inf);//当前时刻的地球向下一时刻的地球连边
for(int i=1;i<=m;i++)
{
int x=(ans-1)%num[i];//上一秒的第i艘飞船应该走到第几个太空站点
int y=ans%num[i];//对应这一秒的第i艘飞船第几个太空站点
if(ship[i][x]==n+2)
x=t;
else
x=(ans-1)*(n+1)+ship[i][x];
if(ship[i][y]==n+2)
y=t;
else
y=ans*(n+1)+ship[i][y];
add_edge(x,y,people[i]);//上一秒的飞船在的站向下一秒飞船在的站连边
}
nowpeople+=dinic(s,t);
if(nowpeople>=k)
{
cout<<ans<<endl;
return 0;
}
for(int i=1;i<=n+1;++i) add_edge((ans-1)*(n+1)+i,ans*(n+1)+i,inf);//时间的转移,空间站的人可以不跟着走留在那,所以每秒空间站向下一秒连条无穷的边
}
return 0;
}
14、孤岛营救问题
这题也不是网络流呢
状压+bfs,在洛谷看到了一个写的很优美的代码题解,照着写了下放在这里
#include<bits/stdc++.h>
using namespace std;
const int N=20;
int g[N][N][N][N],key[N][N][N],cnt[N][N]={0};
int vis[N][N][1<<N];
int dx[4]={0,0,1,-1},dy[4]={1,-1,0,0};
int n,m,p;
int ans=0;
struct node{
int x,y,k,d;
node() {x=y=k=d=0;}
node(int _x,int _y,int _k,int _d) {
x=_x,y=_y,k=_k,d=_d;
}
};
int getkey(int x,int y)
{
int ans=0;
for(int i=0;i<cnt[x][y];i++)
{
ans=ans|(1<<(key[x][y][i]));
}
return ans;
}
int bfs(int x,int y)
{
queue<node> q;
int sk=getkey(x,y);
q.push(node(x,y,sk,0));
vis[x][y][sk]=1;
while(!q.empty())
{
node v=q.front();q.pop();
if(v.x==n&&v.y==m) return v.d;
for(int i=0;i<4;i++)
{
int nx=v.x+dx[i];
int ny=v.y+dy[i];
int op=g[v.x][v.y][nx][ny];
if(nx<1||nx>n||ny<1||ny>m||op<0||(op&&!(v.k&(1<<(op))))) continue;
int next=v.k|getkey(nx,ny);
if(vis[nx][ny][next]) continue;
q.push(node(nx,ny,next,v.d+1));
vis[nx][ny][next]=1;
}
}
return -1;
}
int main()
{
cin>>n>>m>>p;
int x1,x2,y1,y2,door,q,k;
cin>>k;
while(k--)
{
cin>>x1>>y1>>x2>>y2>>door;
if(door) g[x1][y1][x2][y2]=g[x2][y2][x1][y1]=door;
else g[x1][y1][x2][y2]=g[x2][y2][x1][y1]=-1;//墙
}
int s;
cin>>s;
for(int i=1;i<=s;i++)
{
cin>>x1>>y1>>q;
key[x1][y1][cnt[x1][y1]]=q;
cnt[x1][y1]++;
}
cout<<bfs(1,1)<<endl;
}
15、汽车加油行驶问题
和上题差不多是个分层bfs就行了
一开始写了个分层bfs没跑出来,后面看了题解发现虽然bfs第一次搜到的路径确实最短,但不一定就是最优的,所以还是要记录一个存费用的数组,哪怕一个点访问过了,只要费用更小还是要进去访问并覆盖掉的
这题代码给的是我看的那篇洛谷的题解的,还不是很懂为什么加油那里上面要K=k下面不用,加了就wa
#include<bits/stdc++.h>
using namespace std;
int dis[110][110][15],init[110][110][15];
int n,k,a,b,c,ma[110][110];
struct node
{
int x,y,k;
};
queue<node> q;
int dx[4]={-1,0,1,0};
int dy[4]={0,1,0,-1};
int main()
{
scanf("%d%d%d%d%d",&n,&k,&a,&b,&c);
for(int i=1;i<=n;++i)
for(int j=1;j<=n;++j)
scanf("%d",&ma[i][j]);
memset(dis,20,sizeof(dis));
dis[1][1][k]=0;
init[1][1][k]=1;
q.push((node){1,1,k});
while(!q.empty())
{
int X=q.front().x;
int Y=q.front().y;
int K=q.front().k;q.pop();
init[X][Y][K]=0;
if(ma[X][Y]&&K!=k)
{
if(dis[X][Y][k]>dis[X][Y][K]+a)
{
dis[X][Y][k]=dis[X][Y][K]+a;
if(!init[X][Y][k])
init[X][Y][k]=1,q.push((node){X,Y,k});
K=k;
}
}
else
{
if(dis[X][Y][k]>dis[X][Y][K]+a+c)
{
dis[X][Y][k]=dis[X][Y][K]+a+c;
if(!init[X][Y][k])
init[X][Y][k]=1,q.push((node){X,Y,k});
}
}
if(K>0)//开车
for(int i=0;i<4;++i)
{
int x=X+dx[i];
int y=Y+dy[i];
if(x<1||x>n||y<1||y>n) continue;
int len=0;
if(x<X||y<Y) len=b;
if(dis[x][y][K-1]>dis[X][Y][K]+len)
{
dis[x][y][K-1]=dis[X][Y][K]+len;
if(!init[x][y][K-1])
init[x][y][K-1]=1,q.push((node){x,y,K-1});
}
}
}
int ans=(1<<30);
for(int i=0;i<=k;++i)
ans=min(ans,dis[n][n][i]);
printf("%d\n",ans);
return 0;
}
16、数字梯形问题
这题只是看着复杂,其实很简单的
建三次图跑三次最大费用最大流就好了
主要是建了三次图所以代码会很长
第一问:因为结点和边都不能相交,所以我们向以前一样拆点位入点和出点,把入点和出点连一条容量为1的边权值为点权即可(权值为负就是跑最大费用)
第二问:结点可以相交,所以我们只要直接连边,容量设为1即可
第三问:没有限制了,直接建图跑就行了
#include <bits/stdc++.h>
using namespace std;
#define inf 0x3f3f3f3f
typedef long long ll;
typedef pair<int,int> P;//first保存最短距离,second保存顶点编号
const int maxn=100050;
struct edge
{
int to;
ll cap;
ll cost;
int rev;
};
vector<edge> g[maxn];//图的邻接表表示
int h[maxn];//顶点的势
int dist[maxn];//最短距离
int prevv[maxn],preve[maxn];//最短路中的前驱节点和对应的边
int a[1001][1001],b[1001][1001],cnt;
int n;//顶点数
int m,s,t;
ll maxflow,mincost;
void init(int x)
{
for(int i=0;i<=x;i++)
g[i].clear();
maxflow=0;
mincost=0;
}
void add_edge(int from, int to, int cap, int cost)
{
g[from].push_back((edge){to,cap,cost,g[to].size()});
g[to].push_back((edge){from,0,-cost,g[from].size()-1});
}
//求解从s到t流量为f的最小费用流
//如果没有流量为f的流,则返回-1
//Dijkstra算法
int min_cost_flow(int s,int t,int f)
{
memset(h,0,sizeof(h));//初始化h
while(f>0){
//使用dijkstra算法更新h
priority_queue<P,vector<P>,greater<P> > que;
memset(dist,inf,sizeof(dist));
dist[s]=0;
que.push(P(0,s));
while(!que.empty())
{
P now=que.top();que.pop();
if(dist[now.second]<now.first) continue;
int v=now.second;
for(int i=0;i<g[v].size();i++)
{
edge &e=g[v][i];
if(e.cap>0&&dist[e.to]>dist[v]+e.cost+h[v]-h[e.to])
{
dist[e.to]=dist[v]+e.cost+h[v]-h[e.to];
prevv[e.to]=v;
preve[e.to]=i;
que.push(P(dist[e.to],e.to));
}
}
}
if(dist[t]==inf)//不能再增广
{
break;
}
for(int i=1;i<=t;i++) h[i]+=dist[i];
//沿s到t的最短路尽量推广
ll d=f;
for(int v=t;v!=s;v=prevv[v])
{
d=min(d,g[prevv[v]][preve[v]].cap);
}
f-=d;
maxflow+=d;
mincost+=d*h[t];
for(int v=t;v!=s;v=prevv[v])
{
edge &e=g[prevv[v]][preve[v]];
e.cap-=d;
g[v][e.rev].cap+=d;
}
}
return 0;
}
int main()
{
ios::sync_with_stdio(false);
cin>>m>>n;
for(int i=1;i<=n;i++)
for(int j=1;j<=m+i-1;j++)
cin>>a[i][j],b[i][j]=++cnt;
s=0;
t=cnt*2+3;
for(int i=1;i<=m;i++)
add_edge(s,b[1][i],1,0);
for(int i=1;i<n;i++)//向左下向右下走
for(int j=1;j<=m+i-1;j++)
{
add_edge(b[i][j],b[i][j]+cnt,1,-a[i][j]);
add_edge(b[i][j]+cnt,b[i+1][j],1,0);
add_edge(b[i][j]+cnt,b[i+1][j+1],1,0);
}
for(int i=1;i<=m+n-1;i++)
{
add_edge(b[n][i],b[n][i]+cnt,1,-a[n][i]);
add_edge(b[n][i]+cnt,t,1,0);
}
min_cost_flow(s,t,inf);
cout<<-mincost<<endl;
init(t);
for(int i=1;i<=m;i++)
add_edge(s,b[1][i],1,0);
for(int i=1;i<n;i++)//向左下向右下走
for(int j=1;j<=m+i-1;j++)
{
add_edge(b[i][j],b[i+1][j],1,-a[i][j]);
add_edge(b[i][j],b[i+1][j+1],1,-a[i][j]);
}
for(int i=1;i<=m+n-1;i++)
{
add_edge(b[n][i],t,inf,-a[n][i]);
}
min_cost_flow(s,t,inf);
cout<<-mincost<<endl;
init(t);
for(int i=1;i<=m;i++)
add_edge(s,b[1][i],1,0);
for(int i=1;i<n;i++)//向左下向右下走
for(int j=1;j<=m+i-1;j++)
{
add_edge(b[i][j],b[i+1][j],inf,-a[i][j]);
add_edge(b[i][j],b[i+1][j+1],inf,-a[i][j]);
}
for(int i=1;i<=m+n-1;i++)
{
add_edge(b[n][i],t,inf,-a[n][i]);
}
min_cost_flow(s,t,inf);
cout<<-mincost<<endl;
return 0;
}
17、运输问题
这题比之前简单好多,这可太简单了,就是一个标准的板子题
直接看代码吧,就是跑一次最小费用最大流再跑一次最大费用最大流,建图直接按题面建图就好了
#include <bits/stdc++.h>
using namespace std;
#define inf 0x3f3f3f3f
typedef long long ll;
typedef pair<int,int> P;//first保存最短距离,second保存顶点编号
const int maxn=20050;
struct edge
{
int to;
ll cap;
ll cost;
int rev;
};
vector<edge> g[maxn];//图的邻接表表示
int h[maxn];//顶点的势
int dist[maxn];//最短距离
int prevv[maxn],preve[maxn];//最短路中的前驱节点和对应的边
int n;//顶点数
int m,s,t;
ll maxflow,mincost;
int a[maxn],b[maxn],c[maxn][maxn];
void init(int x)
{
for(int i=0;i<=x;i++)
{
g[i].clear();
}
mincost=maxflow=0;
}
void add_edge(int from, int to, int cap, int cost)
{
g[from].push_back((edge){to,cap,cost,g[to].size()});
g[to].push_back((edge){from,0,-cost,g[from].size()-1});
}
//求解从s到t流量为f的最小费用流
//如果没有流量为f的流,则返回-1
//Dijkstra算法
int min_cost_flow(int s,int t,int f)
{
memset(h,0,sizeof(h));//初始化h
while(f>0){
//使用dijkstra算法更新h
priority_queue<P,vector<P>,greater<P> > que;
memset(dist,inf,sizeof(dist));
dist[s]=0;
que.push(P(0,s));
while(!que.empty())
{
P now=que.top();que.pop();
if(dist[now.second]<now.first) continue;
int v=now.second;
for(int i=0;i<g[v].size();i++)
{
edge &e=g[v][i];
if(e.cap>0&&dist[e.to]>dist[v]+e.cost+h[v]-h[e.to])
{
dist[e.to]=dist[v]+e.cost+h[v]-h[e.to];
prevv[e.to]=v;
preve[e.to]=i;
que.push(P(dist[e.to],e.to));
}
}
}
if(dist[t]==inf)//不能再增广
{
break;
}
for(int i=0;i<=t+1;i++) h[i]+=dist[i];
//沿s到t的最短路尽量推广
ll d=f;
for(int v=t;v!=s;v=prevv[v])
{
d=min(d,g[prevv[v]][preve[v]].cap);
}
f-=d;
maxflow+=d;
mincost+=d*h[t];
for(int v=t;v!=s;v=prevv[v])
{
edge &e=g[prevv[v]][preve[v]];
e.cap-=d;
g[v][e.rev].cap+=d;
}
}
return 0;
}
int main()
{
ios::sync_with_stdio(false);
cin>>m>>n;
s=0;
t=m+n+5;
for(int i=1;i<=m;i++)
{
cin>>a[i];
add_edge(s,i,a[i],0);
}
for(int i=1;i<=n;i++)
{
cin>>b[i];
add_edge(m+i,t,b[i],0);
}
for(int i=1;i<=m;i++)
{
for(int j=1;j<=n;j++)
{
cin>>c[i][j];
add_edge(i,m+j,inf,c[i][j]);
}
}
min_cost_flow(s,t,inf);
cout<<mincost<<endl;
init(t+10);
for(int i=1;i<=m;i++)
{
add_edge(s,i,a[i],0);
}
for(int i=1;i<=n;i++)
{
add_edge(m+i,t,b[i],0);
}
for(int i=1;i<=m;i++)
{
for(int j=1;j<=n;j++)
{
add_edge(i,m+j,inf,-c[i][j]);
}
}
min_cost_flow(s,t,inf);
cout<<-mincost<<endl;
return 0;
}
18、分配问题
和上题基本一样,很简单的题,直接贴代码了
#include <bits/stdc++.h>
using namespace std;
#define inf 0x3f3f3f3f
typedef long long ll;
typedef pair<int,int> P;//first保存最短距离,second保存顶点编号
const int maxn=20050;
struct edge
{
int to;
ll cap;
ll cost;
int rev;
};
vector<edge> g[maxn];//图的邻接表表示
int h[maxn];//顶点的势
int dist[maxn];//最短距离
int prevv[maxn],preve[maxn];//最短路中的前驱节点和对应的边
int n;//顶点数
int m,s,t;
ll maxflow,mincost;
int a[maxn],b[maxn],c[maxn][maxn];
void init(int x)
{
for(int i=0;i<=x;i++)
{
g[i].clear();
}
mincost=maxflow=0;
}
void add_edge(int from, int to, int cap, int cost)
{
g[from].push_back((edge){to,cap,cost,g[to].size()});
g[to].push_back((edge){from,0,-cost,g[from].size()-1});
}
//求解从s到t流量为f的最小费用流
//如果没有流量为f的流,则返回-1
//Dijkstra算法
int min_cost_flow(int s,int t,int f)
{
memset(h,0,sizeof(h));//初始化h
while(f>0){
//使用dijkstra算法更新h
priority_queue<P,vector<P>,greater<P> > que;
memset(dist,inf,sizeof(dist));
dist[s]=0;
que.push(P(0,s));
while(!que.empty())
{
P now=que.top();que.pop();
if(dist[now.second]<now.first) continue;
int v=now.second;
for(int i=0;i<g[v].size();i++)
{
edge &e=g[v][i];
if(e.cap>0&&dist[e.to]>dist[v]+e.cost+h[v]-h[e.to])
{
dist[e.to]=dist[v]+e.cost+h[v]-h[e.to];
prevv[e.to]=v;
preve[e.to]=i;
que.push(P(dist[e.to],e.to));
}
}
}
if(dist[t]==inf)//不能再增广
{
break;
}
for(int i=0;i<=t+1;i++) h[i]+=dist[i];
//沿s到t的最短路尽量推广
ll d=f;
for(int v=t;v!=s;v=prevv[v])
{
d=min(d,g[prevv[v]][preve[v]].cap);
}
f-=d;
maxflow+=d;
mincost+=d*h[t];
for(int v=t;v!=s;v=prevv[v])
{
edge &e=g[prevv[v]][preve[v]];
e.cap-=d;
g[v][e.rev].cap+=d;
}
}
return 0;
}
int main()
{
ios::sync_with_stdio(false);
cin>>n;
s=0;
t=2*n+5;
for(int i=1;i<=n;i++)
{
for(int j=1;j<=n;j++)
{
cin>>c[i][j];
add_edge(i,n+j,1,c[i][j]);//人到工作
}
}
//源点到人
for(int i=1;i<=n;i++)
add_edge(s,i,1,0);
//工作到汇点
for(int i=1;i<=n;i++)
add_edge(n+i,t,1,0);
min_cost_flow(s,t,inf);
cout<<mincost<<endl;
init(t+10);
for(int i=1;i<=n;i++)
{
for(int j=1;j<=n;j++)
{
add_edge(i,n+j,1,-c[i][j]);
}
}
//源点到人
for(int i=1;i<=n;i++)
add_edge(s,i,1,0);
for(int i=1;i<=n;i++)
add_edge(n+i,t,1,0);
min_cost_flow(s,t,inf);
cout<<-mincost<<endl;
return 0;
}
19、负载平衡问题
这题也是很简单的
我们考虑把每个点拆成入点和出点
建图:
1、s向每个入点连容量为初始货量费用为0的边
2、每个出点向t连容量为货量平均值费用为0的边
3、入点向出点连无穷大费用为0的边,表示货物可以留守
4、出点向每个相邻入点连无穷大费用为1的边,表示货物转移
跑一遍最小费用最大流就好了,最大流保证能够平衡货物,而最小费用流能保证运输的货物最少
#include <bits/stdc++.h>
using namespace std;
#define inf 0x3f3f3f3f
typedef long long ll;
typedef pair<int,int> P;//first保存最短距离,second保存顶点编号
const int maxn=20050;
struct edge
{
int to;
ll cap;
ll cost;
int rev;
};
vector<edge> g[maxn];//图的邻接表表示
int h[maxn];//顶点的势
int dist[maxn];//最短距离
int prevv[maxn],preve[maxn];//最短路中的前驱节点和对应的边
int n;//顶点数
int m,s,t;
ll maxflow,mincost;
void init(int x)
{
for(int i=0;i<=x;i++)
{
g[i].clear();
}
mincost=maxflow=0;
}
void add_edge(int from, int to, int cap, int cost)
{
g[from].push_back((edge){to,cap,cost,g[to].size()});
g[to].push_back((edge){from,0,-cost,g[from].size()-1});
}
//求解从s到t流量为f的最小费用流
//如果没有流量为f的流,则返回-1
//Dijkstra算法
int min_cost_flow(int s,int t,int f)
{
memset(h,0,sizeof(h));//初始化h
while(f>0){
//使用dijkstra算法更新h
priority_queue<P,vector<P>,greater<P> > que;
memset(dist,inf,sizeof(dist));
dist[s]=0;
que.push(P(0,s));
while(!que.empty())
{
P now=que.top();que.pop();
if(dist[now.second]<now.first) continue;
int v=now.second;
for(int i=0;i<g[v].size();i++)
{
edge &e=g[v][i];
if(e.cap>0&&dist[e.to]>dist[v]+e.cost+h[v]-h[e.to])
{
dist[e.to]=dist[v]+e.cost+h[v]-h[e.to];
prevv[e.to]=v;
preve[e.to]=i;
que.push(P(dist[e.to],e.to));
}
}
}
if(dist[t]==inf)//不能再增广
{
break;
}
for(int i=0;i<=t+1;i++) h[i]+=dist[i];
//沿s到t的最短路尽量推广
ll d=f;
for(int v=t;v!=s;v=prevv[v])
{
d=min(d,g[prevv[v]][preve[v]].cap);
}
f-=d;
maxflow+=d;
mincost+=d*h[t];
for(int v=t;v!=s;v=prevv[v])
{
edge &e=g[prevv[v]][preve[v]];
e.cap-=d;
g[v][e.rev].cap+=d;
}
}
return 0;
}
int main()
{
ios::sync_with_stdio(false);
cin>>n;
s=0;
t=2*n+5;
int sum=0;
int x[150];
for(int i=1;i<=n;i++)
{
cin>>x[i];
sum+=x[i];
add_edge(s,i,x[i],0);
}
for(int i=1;i<=n;i++)
add_edge(i,i+n,inf,0);
for(int i=1;i<=n;i++)
add_edge(n+i,t,sum/n,0);
for(int i=1;i<=n;i++)
{
if(i==1)
{
add_edge(n+1,n,inf,1);
add_edge(n+1,2,inf,1);
}
else if(i==n)
{
add_edge(2*n,n-1,inf,1);
add_edge(2*n,1,inf,1);
}
else
{
add_edge(n+i,i-1,inf,1);
add_edge(n+i,i+1,inf,1);
}
}
min_cost_flow(s,t,inf);
cout<<mincost<<endl;
return 0;
}
20、深海机器人问题
这题也不难,就是坐标麻烦了点
建图,连接源点到所有机器人位置,容量为这个点的机器人数量,费用为0
连接机器人终点到汇点,容量为能走的机器人数量,费用为0
连接每个点到他右边的点,两条边,一条容量为1,费用为点权,一条容量为inf,费用为0,代表这个边的东西已经被取走了,但依然是能走的
连接每个点到他上边的点,方法同连右边
跑个最大费用最大流即可
#include <bits/stdc++.h>
using namespace std;
#define inf 0x3f3f3f3f
typedef long long ll;
typedef pair<int,int> P;//first保存最短距离,second保存顶点编号
const int maxn=20050;
struct edge
{
int to;
ll cap;
ll cost;
int rev;
};
vector<edge> g[maxn];//图的邻接表表示
int h[maxn];//顶点的势
int dist[maxn];//最短距离
int prevv[maxn],preve[maxn];//最短路中的前驱节点和对应的边
int n;//顶点数
int m,s,t;
ll maxflow,mincost;
void init(int x)
{
for(int i=0;i<=x;i++)
{
g[i].clear();
}
mincost=maxflow=0;
}
void add_edge(int from, int to, int cap, int cost)
{
g[from].push_back((edge){to,cap,cost,g[to].size()});
g[to].push_back((edge){from,0,-cost,g[from].size()-1});
}
//求解从s到t流量为f的最小费用流
//如果没有流量为f的流,则返回-1
//Dijkstra算法
int min_cost_flow(int s,int t,int f)
{
memset(h,0,sizeof(h));//初始化h
while(f>0){
//使用dijkstra算法更新h
priority_queue<P,vector<P>,greater<P> > que;
memset(dist,inf,sizeof(dist));
dist[s]=0;
que.push(P(0,s));
while(!que.empty())
{
P now=que.top();que.pop();
if(dist[now.second]<now.first) continue;
int v=now.second;
for(int i=0;i<g[v].size();i++)
{
edge &e=g[v][i];
if(e.cap>0&&dist[e.to]>dist[v]+e.cost+h[v]-h[e.to])
{
dist[e.to]=dist[v]+e.cost+h[v]-h[e.to];
prevv[e.to]=v;
preve[e.to]=i;
que.push(P(dist[e.to],e.to));
}
}
}
if(dist[t]==inf)//不能再增广
{
break;
}
for(int i=0;i<=t+1;i++) h[i]+=dist[i];
//沿s到t的最短路尽量推广
ll d=f;
for(int v=t;v!=s;v=prevv[v])
{
d=min(d,g[prevv[v]][preve[v]].cap);
}
f-=d;
maxflow+=d;
mincost+=d*h[t];
for(int v=t;v!=s;v=prevv[v])
{
edge &e=g[prevv[v]][preve[v]];
e.cap-=d;
g[v][e.rev].cap+=d;
}
}
return 0;
}
int main()
{
int a,b;
cin>>a>>b;
int p,q;
cin>>p>>q;
s=500;t=501;
for(int i=0;i<=p;i++)
{
for(int j=0;j<q;j++)
{
int x;
cin>>x;
add_edge(i*(q+1)+j,i*(q+1)+j+1,1,-x);
add_edge(i*(q+1)+j,i*(q+1)+j+1,inf,0);
}
}
for(int j=0;j<=q;j++)
{
for(int i=0;i<p;i++)
{
int x;
cin>>x;
add_edge(i*(q+1)+j,(i+1)*(q+1)+j,1,-x);
add_edge(i*(q+1)+j,(i+1)*(q+1)+j,inf,0);
}
}
for(int i=1;i<=a;i++)
{
int k,x,y;
cin>>k>>x>>y;
add_edge(s,x*(q+1)+y,k,0);
}
for(int i=1;i<=b;i++)
{
int k,x,y;
cin>>k>>x>>y;
add_edge(x*(q+1)+y,t,k,0);
}
min_cost_flow(s,t,inf);
cout<<-mincost<<endl;
return 0;
}