原先看到有神仙写过,感觉挺不错的,自己也来写写看。
题目花了挺长时间,感觉做得差不多了。
只有 (4) 道题没做,除了那道假题之外剩下的都是非网络流题(大概吧),以后有时间可能会去做做。
以下题目顺序按洛谷难度排序。
除非必要情况,其他的不展示代码。
题目及思路
飞行员配对方案问题
Description
一共有 (n) 个飞行员,其中有 (m) 个外籍飞行员和 ((n - m)) 个英国飞行员,外籍飞行员从 (1) 到 (m) 编号,英国飞行员从 (m + 1) 到 (n) 编号。
对于给定的外籍飞行员与英国飞行员的配合情况,试设计一个算法找出最佳飞行员配对方案,使皇家空军一次能派出最多的飞机。
Solution
求二分图最大匹配数。
把 (m) 个外国人放左边,((n-m)) 个英国人放右边,然后源点向左边点连容量 (1) 的边,左边点向能匹配的右边点连容量为 (1) 的边,右边点向汇点连容量为 (1) 的边。
最后输出最大流即可。
负载平衡问题
环形均分纸牌。
szt 说这题与 [HAOI2008]糖果传递 是一样的,贪心即可。
最长 k 可重区间集问题
Description
给定一个区间集合,集合中的区间有可能会重叠。要求从这个集合中选出一些区间组成一个新的区间集合,并保证在数轴上的任意一点被这些区间覆盖的次数都小于 (k)。
求这个新的区间集合的最长总长度。
Solution
显然用费用流求解。
考虑把区间数当做流量,长度当做费用。
由于区间点可能会出现多次,所以对于区间上的所有点,先进行离散化。
考虑到每段区间的覆盖是从最左端一直到最右端,而且每走完一段区间集合中的区间数就加一,那么就对于每端区间从左端点到右端点连一条容量为 (1),费用为左端到右端长度的边。
又每个点最多被覆盖 (k) 次,所以每两个相临的点之间都要连容量为 (k),费用为 (0) 的边。
然后源点向最左边的点连容量为 (k) ,费用为 (0) 的边,最右边的点向汇点连容量为 (k) ,费用为 (0) 的边。
注意这题不要因为所谓的数轴和区间而思维定式了,注意连完后的是张图,跑出来的最大权值就是最长 k 可重区间集总长度,最大流就是区间数,所以只需要考虑对于每段区间怎么转移就好了。
分配问题
Description
有 (n) 件工作要分配给 (n) 个人做。第 (i) 个人做第 (j) 件工作产生的效益为 (c_{ij})。
试设计一个将 (n) 件工作分配给 (n) 个人做的分配方案,使产生的总效益最大。
Solution
没记错的话,这题要求的是二分图最大权完美匹配和最小权完美匹配(好像是这么叫),所以用费用流求解。
那其实就和上面 飞行员配对方案问题 的建法基本一致。
把工件和人当做两边的点,然后源点向人连容量为 (1),费用为 (0) 的边,工件向汇点连容量为 (1),费用为 (0) 的边,再从每个人分别向每个工件连容量为 (1),费用为这个人与这个工件匹配所获得的价值的边。
最后跑两边费用流就好了。
方格取数问题
Description
有一个 (m) 行 (n) 列的方格图,每个方格中都有一个正整数。
现要从方格中取数,使任意两个数所在方格没有公共边,且取出的数的总和最大,请求出最大的和。
(题目都挺简洁都不用我概括了蛤)
Solution
首先有个小结论:一定要取 (largefrac{n imes m}{2}) 个点才能使答案最优。理由是所有权值都大于零。
另外换个角度,想要取出的最大,不如在保证数量的范围内删去小的,也就是最小割。
又因为最大流等于最小割,那跑最大流好了。
至于连边,我们肯定能想到,是要把所有格子平均分成左右两部分,其中左边向右边连边,然后连源点汇点。
但是显然的,我们必须从每个方格向在它周围与它能一起取的方格连边,而能与它一起取的方格与它都不能有公共边。
所以我们可以对格子进行黑白染色,以黑白交替的方式使方格点分为黑和白两大类。
源点向黑格子连容量为当前格子权值的边,黑格子向能到达的白格子连容量无穷大的边,白格子点向汇点连容量为当前格子权值的边。
赋值为无穷大是为了防止被删,而删去一条边是表示不选这个格子。
这样被删的边只可能是两边与源点和汇点直接相连的边,而且是不优的边。
最后答案就是总权值减去最大流(最小割)。
汽车加油行驶问题
Description
概括不过来。
Solution
大概可以用费用流求解,可惜当时做的时候我还不会网络流,于是就用了最短路。
处理一下转移。
有加油站一定加油,所以如果在加油站并且油不是满的,那肯定得先加一发油。
然后枚举四个方向转移一下,求个最短路就好了。
既然不是网络流做的就不详细说了,给个代码。
#include<bits/stdc++.h>
#define maxn 110
#define LL long long
#define INF 0x7fffffff
using namespace std;
int n,k,a,b,c,vis[maxn][maxn][11];
int Map[maxn][maxn],Ano[maxn][maxn][11];
int dx[4]={1,0,-1,0},dy[4]={0,1,0,-1};
priority_queue<pair<int,pair<int,pair<int,int> > > > q;
inline int read(){
int s=0,w=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-') w=-1;ch=getchar();}
while(ch>='0'&&ch<='9')s=(s<<1)+(s<<3)+ch-'0',ch=getchar();
return s*w;
}
inline void Dijkstra(){
memset(Ano,0x7f,sizeof Ano);Ano[1][1][k]=0;
q.push(make_pair(0,make_pair(k,make_pair(1,1))));
while(!q.empty()){
int Now=q.top().second.first;
int x=q.top().second.second.first;
int y=q.top().second.second.second;
q.pop();if(!vis[x][y][Now]){
vis[x][y][Now]=1;
for(register int i=0;i<4;i++){
int Tx=x+dx[i],Ty=y+dy[i],Tnow=Now-1;
int Money=Ano[x][y][Now];
if(Tx>=1&&Tx<=n&&Ty>=1&&Ty<=n){
if(Map[Tx][Ty]==1)Money+=a,Tnow=k;
if(!Tnow)Money+=c+a,Tnow=k;
if(i>=2)Money+=b;
if(Ano[Tx][Ty][Tnow]>Money){
Ano[Tx][Ty][Tnow]=Money;
q.push(make_pair(-Money,make_pair(Tnow,make_pair(Tx,Ty))));
}
}
}
}
}
int Minx=INF;
if(Ano[n][n][k]!=INF)Ano[n][n][0]=Ano[n][n][k]-c-a;
for(register int i=0;i<k;i++)Minx=min(Minx,Ano[n][n][i]);
printf("%d",Minx);return;
}
int main(){
n=read();k=read();
a=read();b=read();c=read();
for(register int i=1;i<=n;i++)
for(register int j=1;j<=n;j++)
Map[i][j]=read();
Dijkstra();return 0;
}
运输问题
Description
给出 (n) 个商店和 (m) 个仓库,仓库需要向商店运送货物,第 (i) 个仓库有 (a_i) 个货物,第 (i) 个商店需要 (b_i) 个货物,第 (i) 个仓库向第 (j) 个商店运一个货物需要 (c_{i,j}) 的费用。
分别求出将仓库中所有货物运送到零售商店的前提下,最多的花费和最少的花费。
Solution
显然费用流。
把商店和仓库分做两边的点,源点向商店连容量为商品数量,费用为 (0) 的边,仓库向汇点连容量为商品数量,费用为 (0) 的边,最后每个商店向每个仓库连容量无穷大,费用为运费的边即可。
太空飞行计划问题
Description
有 (m) 个实验,每个实验只可以进行一次,但会获得相应的收入,有 (n) 个仪器,每个实验都需要一定的仪器,每个仪器可以运用于多个实验,但需要一定的花费。
问收入减去花费的最大值是多少。
Solution
题目难度 提高-,输入难度 IOI+。
首先,不要因为题目中有费用就以为要用费用流。
这题无非就是衡量仪器与实验之间的利益关系,显然是求最小割。
割去某些实验后,可以少买一些仪器,使得净收益最大。
那就从源点向实验连容量为实验收入的边,然后实验向它所需的仪器连容量无限大的边,仪器向汇点连容量为购买费用的边。
最大花费就是总收益减去最大流。
至于输出方案,我们可以知道,当最大流跑完后,如果某条边上的流量为 (0),那么证明跑最大流时选择了这条边,而与这条边相连的点的容量一定会有。
因此我们可以直接根据 (dis) 数组输出方案。
给个主函数代码。
int main(){
m=read();n=read();
s=n+m+1;t=n+m+2;
for(int i=1,v;i<=m;i++){
v=read();all+=v;add(s,i,v);add(i,s,0);
char tools[10000];memset(tools,0,sizeof tools);
cin.getline(tools,10000);int ulen=0,tool,now=0;
while (sscanf(tools+ulen,"%d",&tool)==1){
if(tool==0){now=0;ulen++;}
else while(tool){
add(i,tool+m,INF),add(tool+m,i,0);
now+=tool%10;tool/=10;ulen++;
}
ulen++;
}
}
for(int i=1;i<=n;i++)
val[i]=read(),add(i+m,t,val[i]),add(t,i+m,0);int ans=dinic();
for(int i=1;i<=m;i++)if(Dis[i]!=-1)printf("%d ",i);printf("
");
for(int i=1;i<=n;i++)if(Dis[i+m]!=-1)printf("%d ",i);printf("
");
printf("%d
",all-ans);return 0;
}
航空路线问题
Description
说不清楚自己看。
Solution
拆点费用流。
因为每个点只能选一次,每选一个点可以对答案造成 (1) 的贡献,所以我们可以把每个点拆成出点和入点,同时入点向出点连容量为 (1),费用为 (1) 的边。
同时对于每个点,从它的出点向它能直接到达的下一个点的入点连容量为 (1) ,费用为 (0) 的边。如果这条边的起点是最西边的点或者终点是最东边的点,那么这条边的容量应该是 (2),因为根据题意,它可以走两遍。
跑出最大流判断一下是否小于 (2),若小于则代表无解,否则答案为最大费用 (+2)。
至于方案,可以跑两遍 DFS。
第一遍找出 (1) 到 (n) 流量为 (0) 的路径正序输出,第二遍找出另一条流量为 (0) 的路径倒序输出,流量为 (0) 的原因同上。
代码
int main(){
n=read();m=read();
for(int i=1;i<=n;i++){
string name;cin>>name;mp[name]=i;S[i]=name;
add(i,i+n,(i==1||i==n)?2:1,-1),add(i+n,i,0,1);
}
for(int i=1;i<=m;i++){
string name1,name2;cin>>name1>>name2;
int x=mp[name1],y=mp[name2];if(x>y) swap(x,y);
add(x+n,y,(x==1&&y==n)?2:1,0),add(y,x+n,0,0);
}
s=1;t=n*2;int ans=dinic();
if(ans<2){
printf("No Solution!
");
return 0;
}
else printf("%d
",-res-2);
dfs2(1);for(int i=1;i<=cnt;i++) cout<<S[Ans[i]]<<endl;
cnt=0;dfs2(1);for(int i=cnt;i;i--) cout<<S[Ans[i]]<<endl;
return 0;
}
圆桌问题
Description
(m) 个单位,每个单位派出一定数量的代表,(n) 张桌子,每张桌子可容纳一定数量的人。
求出使得在每张桌子上不出现两个来自同一单位的人的一种合法方案。
Solution
最大流,连边方法比较简单。
源点向每个单位连容量为单位人数的边,桌子向汇点连容量为能容纳人数的边。
然后每个单位分别向每个桌子连容量为 (1) 的边,表示一个桌子只能坐这个单位的一个人。
这样求出最大流,如果等于总人数,则表示存在合法方案。
至于枚举方案,就先枚举每个单位,然后根据最大流跑过的边流量为 (0) 的性质找出这个单位的人坐了哪些桌子即可。
深海机器人问题
Description
给定一个网格图,图上会出现一些机器人,每个机器人只能向北或向东走,而且在某个位置向某个方向走会得到对应的分数,但每个位置的分数只能取一次。
给出一些会有机器人出现的点和另外一些会有机器人离开的点。
求能获得的最大得分。
Solution
原先写过题解,比较详细(大概吧),就直接粘了。
首先显然的可以看出,一个机器人可以代表一个流量,每获得一分可以代表费用加一。
看到题目要求的不是有多少个机器人能离开,而是能获得多少分,所以选择求最大流下的最大费用。
然后来分析一下怎么建图。
已知会有不确定个点出现不确定个机器人,并且他们的出现并不会增加得分,所以我们可以从源点 (S) 分别向这些起点连流量为机器人个数,费用为 (0) 的边。
同理,对于最多能离开不确定个机器人的终点,从它们向汇点分别连一条流量为机器人个数,费用为 (0) 的边。
对于图中的点,根据机器人的行走方式,我们可以看出机器人只能从西走向东或从南走向北,并且每次只能走一格。
又因为第一次走某个位置都可以获得对应的权值,我们就可以对于每个点,向它所能到达的点(也就是它东边和北边直接相邻的点)连一条流量为 (1),费用为对应权值的边。
走过的边还可以随便走,但是不会再获得分数,所以再从每个点向它所能到达的点连一条流量为无穷大,费用为 (0) 的边。
然后跑费用流就行了。
另外对于方向,虽然题目要求的是向东和向北走,但其实只要纵向和横向分别规定一个方向,然后输入权值和建边的时候与方向对应上即可。
想练习输出方案可以去做 火星探险问题,只是出现了障碍点,其他的差不多。
主函数
int main(){
a=read();b=read();n=read()+1;m=read()+1;int pos=0;s=301;t=s+1;
for(int i=1;i<=n;i++)for(int j=1;j<=m;j++)id[i][j]=++pos;
for(int i=1,val;i<=n;i++)for(int j=2;j<=m;j++)east[i][j]=read();
for(int i=1,val;i<=m;i++)for(int j=2;j<=n;j++)north[j][i]=read();
for(int i=1,k,x,y;i<=a;i++){
k=read();x=read()+1;y=read()+1;
add(s,id[x][y],k,0);
add(id[x][y],s,0,0);
}
for(int i=1,r,x,y;i<=b;i++){
r=read();x=read()+1;y=read()+1;
add(id[x][y],t,r,0);
add(t,id[x][y],0,0);
}
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
if(i+1<=n){
add(id[i][j],id[i+1][j],1,-north[i+1][j]);
add(id[i+1][j],id[i][j],0,north[i+1][j]);
add(id[i][j],id[i+1][j],INF,0);add(id[i+1][j],id[i][j],0,0);
}
if(j+1<=m){
add(id[i][j],id[i][j+1],1,-east[i][j+1]);
add(id[i][j+1],id[i][j],0,east[i][j+1]);
add(id[i][j],id[i][j+1],INF,0);add(id[i][j+1],id[i][j],0,0);
}
}
}
dinic();printf("%d
",-res);
return 0;
}
试题库问题
Description
假设一个试题库中有 (n) 道试题。每道试题都标明了所属类别。同一道题可能有多个类别属性。
现要从题库中抽取 (m) 道题组成试卷。并要求试卷包含指定类型的试题。
试设计一个满足要求的组卷算法。
Solution
设计算法可海星。
老套路了,明眼人一看都懂。
题目和试卷两边分,源点向试卷连容量为 (1) 的边表示每种试题只能选一次,试卷向所需的题目连容量为 (1) 的边表示每种试卷只能包含一道这样的题,题目向汇点连容量为数量的边表示最多可以用这么多道这样的题。
然后跑最大流求最小割就好了。
输出方案的方法与上面 圆桌问题 一样。
数字梯形问题
Description
梯形的第一行有 (m) 个数字。从梯形的顶部的 (m) 个数字开始,在每个数字处可以沿左下或右下方向移动,形成一条从梯形的顶至底的路径。
分别遵守以下规则:
-
从梯形的顶至底的 (m) 条路径互不相交;
-
从梯形的顶至底的 (m) 条路径仅在数字结点处相交;
-
从梯形的顶至底的 (m) 条路径允许在数字结点相交或边相交。
分别求出这三种规则下路径上权值和的最大值。
Solution
首先肯定能看出,是要源点连第一排的点,然后每个点连向下一排它能直接到达的点,然后最后一排点连向汇点。
第一问要求路径互不相交,就把每个点拆成入点出点,然后入点向出点连容量为 (1),费用为当前点的权值的边,再用上面的方法从每个出点向下个点的入点连容量为 (1),费用为 (0) 的边。
第二问允许点相交,就不需要拆点了,直接从每个点向下一个点连容量为 (1),费用为下一个点的权值的边就好了。
最后一问点和边都可以相交,也就是一个点可以连向下面的任何一个点。那就随便怎么连就好了。把第二问中每条边的容量改为无穷大即可。
餐巾计划问题
Description
太难解释了/kk。
Solution
说实话这题真不好想。
首先用费用流求解时比较显然的。
把餐巾数量作为流量,花费作为费用,并把每天分为早上和晚上作为两边的点,因为早上会获得新餐巾,晚上会产生脏餐巾。
至于连边:
-
从源点向每一天晚上连容量为当天所需餐巾数,费用为 (0) 的边,表示这一天晚上产生这么多的脏餐巾。
-
从源点向每一天早上连容量无穷大,费用为餐巾的单价的边,表示每天早上可以无穷买新餐巾。
-
从每一天晚上向第二天晚上连一条容量无穷大,费用为 (0) 的边,表示每天晚上可以将任意数量的脏餐巾留到第二天晚上。
-
从每一天早上向汇点连一条容量为当天所用餐巾数,费用为 (0) 的边,表示每天早上可以提供这些条干净的餐巾。
-
从每一天晚上向第 (n) 天后的早上连一条容量为无穷大,费用为慢洗费用 (s) 的边,表示可以把这一晚上任意数量的脏餐巾送去慢洗部,花费相应的钱,并在第 (m) 天后的早上取回这些新餐巾。
-
快洗部同理。
然后跑费用流就完了。
可能说的不是很清晰,放一下建图过程
signed main(){
n=read();s=n*3+2;t=s+1;for(int i=1;i<=n;i++)val[i]=read();
Ncost=read();Qtime=read();Qcost=read();Stime=read();Scost=read();
for(int i=1;i<=n;i++){
add(s,i+n,val[i],0),add(i+n,s,0,0);
add(i,t,val[i],0),add(t,i,0,0);
add(s,i,INF,Ncost),add(i,s,0,-Ncost);
if(i+Qtime<=n) add(i+n,i+Qtime,INF,Qcost),add(i+Qtime,i+n,0,-Qcost);
if(i+Stime<=n) add(i+n,i+Stime,INF,Scost),add(i+Stime,i+n,0,-Scost);
add(i+n,i+n+1,INF,0);add(i+n+1,i+n,0,0);
}
dinic();
printf("%lld
",res);
return 0;
}
骑士共存问题
Description
在一个有障碍的正方形棋盘上放置尽量多的骑士,同时要保证骑士之间不会互相攻击。
Solution
和上面 方格取数 基本一致,加了障碍限制。求最小割。
先对网格图黑白染色,然后对于非障碍的黑点,从源点向它连容量为 (1) 的边,然后从这个点开始向它能攻击到的地方连容量无穷大的边,最后再从每个非障碍的白点向汇点连容量为 (1) 的边。
最长k可重线段集问题
Description
给定一个线段集合,把这些线段映射到横轴上,映射后的区间有可能会重叠。要求从这个集合中选出一些线段组成一个新的线段集合,并保证在数轴上的任意一点被这些线段映射区间覆盖的次数都小于 (k)。
求这个新的线段集合的最长总长度。
Solution
虽说是线段,二维看起来很高大上,其实就是上面 最长k可重区间集问题 的翻版(连题目描述都是搬过来的)。
因为线段的覆盖也是映射到横轴上的,所以本质还是区间。连边方式也与上面那题一样,只不过注意长度变成了线段的长度而不是区间的。
最长不下降子序列问题
Description
给定正整数序列,求出 LIS 长度、在每个数只取一次的条件下的 LIS 最多个数 以及在上一个条件下第一个和最后一个数可以多次使用时的 LIS 最多个数。
Solution
第一问 DP 求解,然后保留 DP 值。后两问求个数,用最大流。
第二问从源点向每个可以作为 LIS 起点的点(DP 值为 (1) 的点)连容量为 (1) 的边,再从每个点向能紧接在它后面的点(DP 值为当前这个点 (+1) 的点)连容量为 (1) 的边,最后从每个可以作为 LIS 终点的点(DP 值为 LIS 长度的点)向汇点连容量为 (1) 的边。
这样第二问跑出的最大流就是最多的 LIS 数量。
第三问,由于第一个和最后第一个数可以随便取,就把与 (1) 或 (n) 点有关的边容量赋为无穷大,其余的按第二问建法建就好了。
魔术球问题
Description
在 (n) 根柱子上放编号为 (1,2,3,ldots) 的球,要求在使任意一个柱子上任意两个相邻的球的编号和为完全平方数的条件下放尽量多的球,输出球的个数以及其中的一种方案。
Solution
一开始完全看不出来和网络流有啥关系。
一开始猜想是要把柱子和球分别作为点,然后找柱子和球之间的关系连边。
然后搞了半天发现柱子和球没什么关系,同样的组合放哪个柱子都是一样的。
根据相邻的两个球编号和为完全平方数这一条件,转为思考球与球之间的关系。
发现,若两个球可以挨着放(和为完全平方数)就对其连边,这样一整条路径上的球就都可以放到一个柱子上。那么题目就变成求这样建完后的图上的最小路径覆盖。
求最小路径覆盖可以参考下面 最小路径覆盖问题,同时也建议先做完那题再做本题。
思路就是不断向图中加边、加点。加边时,每当增量为 0 就新开一个柱子。直到柱子不够用,此时柱子的数量就是答案。
具体就是源点向每个实点连容量为 1 的边,然后每个虚点向汇点连容量为 1 的边。当有两个球的编号和为完全平方数时,从一个实点向另一个虚点连容量为 1 的边。最后跑最大流即可。
输出答案时,我们遍历所有的实点,如果它没有走过就从它开始走即可。
如果我们发现,当前点的下一个点(虚点)对应的实点没有走过,那么我们就从从这个实点开始继续走,直到所有的点都被遍历完。
放个代码 行末空格杀我
#include<bits/stdc++.h>
#define maxn 51000
#define INF 0x3f3f3f3f
//#define int long long
using namespace std;
bool vis[maxn];
int son[maxn];
int n,tot=1,cnt,s,t,ans,table[maxn];
int Dis[maxn],cur[maxn],head[maxn];
struct{int fr,to,dis,nxt;}e[maxn<<1];
int read(){
int s=0,w=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-') w=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){s=(s<<1)+(s<<3)+ch-'0';ch=getchar();}
return s*w;
}
void add(int fr,int to,int dis){
e[++tot].fr=fr;e[tot].to=to;
e[tot].dis=dis;e[tot].nxt=head[fr];
head[fr]=tot;
}
bool bfs(){
memset(Dis,-1,sizeof Dis);
queue<int> q;q.push(s);
Dis[s]=0;cur[s]=head[s];
while(!q.empty()){
int u=q.front();q.pop();
for(int i=head[u];i;i=e[i].nxt){
int to=e[i].to;
if(Dis[to]==-1&&e[i].dis){
q.push(to);
Dis[to]=Dis[u]+1;
cur[to]=head[to];
if(to==t) return true;
}
}
}
return false;
}
int dfs(int u,int limit){
if(u==t) return limit;int flow=0;
for(int i=cur[u];i&&flow<limit;i=e[i].nxt){
int to=e[i].to;cur[u]=i;
if(Dis[to]==Dis[u]+1&&e[i].dis){
int f=dfs(to,min(e[i].dis,limit-flow));
if(!f) Dis[to]=-1;e[i].dis-=f;e[i^1].dis+=f;
flow+=f;if(to!=t) son[u>>1]=to>>1;
}
}
return flow;
}
int dinic(){
int Maxflow=0,flow=0;
while(bfs())
while(flow=dfs(s,INF))
Maxflow+=flow;
return Maxflow;
}
signed main(){
n=read();
s=0;t=50001;int all=0;
while(all<=n){
cnt++;
for(int i=sqrt(cnt)+1;i*i<(cnt<<1);i++)
add((i*i-cnt)<<1,cnt<<1|1,1),add(cnt<<1|1,(i*i-cnt)<<1,0);
add(s,cnt<<1,1),add(cnt<<1,s,0);
add(cnt<<1|1,t,1);add(t,cnt<<1|1,0);
ans=dinic();if(!ans) table[++all]=cnt;
}
printf("%d
",--cnt);
for(int i=1;i<=n;i++){
if(vis[table[i]]) continue;
printf("%d",table[i]);int now=table[i];
vis[now]=1;
while(son[now]&&son[now]!=t>>1){
printf(" %d",now=son[now]);
vis[now]=1;
}
printf("
");
}
return 0;
}
火星探险问题
Description
太长不说了/cy。
Solution
上面 深海机器人问题 这中题也说到了,其实差不多。加了障碍的限制并要求输出方案。
具体的建图方法与上面那题一样,只要多判断一下不要连到障碍上就好了。
至于方案,题目要求的是每辆车的每一步操作都要输出,就对于每条边,存储的时候多维护一个方向,表示当前车辆是通过朝这个方向转来到达这条边的。
然后枚举每一辆车,通过在残量图中跑 DFS,根据反向弧的流量来判断一条边是否可走,如果可走就随便把它存的方向输出然后继续回溯。
具体放个代码
bool dfs2(int x,int u){
if(u-cnt==id[n][m]) return 1;
for(int i=head[u];i;i=e[i].nxt){
int to=e[i].to;
if(e[i^1].dis>0&&to<=cnt&&e[i].tp){
e[i^1].dis--;printf("%d %d
",x,e[i].tp-1);
if(dfs2(x,to+cnt)) return 1;
}
}
return 0;
}
int main(){
k=read();m=read();n=read();
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++){
id[i][j]=++cnt;
val[i][j]=read();
}
s=cnt*2+1;t=s+1;
add(s,id[1][1],k,0,0);add(id[1][1],s,0,0,0);
add(id[n][m]+cnt,t,INF,0,0);add(t,id[n][m]+cnt,0,0,0);
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
add(id[i][j],id[i][j]+cnt,INF,0,0);
add(id[i][j]+cnt,id[i][j],0,0,0);
if(val[i][j]==2){
add(id[i][j],id[i][j]+cnt,1,-1,0);
add(id[i][j]+cnt,id[i][j],0,1,0);
}
}
}
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
if(i+1<=n&&val[i+1][j]!=1){
add(id[i][j]+cnt,id[i+1][j],INF,0,1);
add(id[i+1][j],id[i][j]+cnt,0,0,1);
}
if(j+1<=m&&val[i][j+1]!=1){
add(id[i][j]+cnt,id[i][j+1],INF,0,2);
add(id[i][j+1],id[i][j]+cnt,0,0,2);
}
}
}
dinic();
for(int i=1;i<=k;i++) dfs2(i,id[1][1]+cnt);
return 0;
}
最小路径覆盖问题
Description
不好描述。
Solution
题目里给题解是怎么回事啊,啊?
其实路径覆盖就是不断合并路径(将两个路径通过一条边首尾相连)的过程。要求最小路径覆盖,就要尽量合并更多的路径。
我们可以知道,每合并两条路径,图中的路径覆盖数就会减少 1,且每个点至多与另一个点合并。那么可以转化为二分图来做。
因此用最大流求解。
首先将每个点分为左边右边两部分,然后源点向左边、右边向汇点连容量为 (1) 的边。对于原图中的每条边,从左边的起点向右边终点连一条容量为 (1) 的边,表示这条边原本就存在。然后求出这张图的最大流即可。
对于求方案,只需要在 DFS 的时候维护一下每个点是由哪个点转移来的,然后沿着这个点的匹配边跳即可。
其实如果看不懂题,就按照它给的提示做就行了。
几道没做的题
孤岛营救问题
状压最短路,其实是最早见过的网络流 24 题,因为 状压不好+当时只想写网络流就没做,现在还一直咕着。
软件补丁问题
同是状压最短路,爬了爬了。
星际转移问题
分层图最大流,要用并查集判无解。也有人通过枚举天数来判有无解。
机器人路径规划问题
还没看,听说是个假题。
总结
好题挺多的,但也有几道是板子题,还有很多题目思路都差不多。
考察了二分图匹配、最大流(最小割)、费用流,有的还要根据题目实际进行拆点什么的。但无一例外是考察建模能力。
学到了挺多关于网络流问题的处理方法,比如各种方案的输出、各种条件下的最大流和费用流、最小路径覆盖以及网格图上的各种操作等等。