p.s:
① 此随笔着重讲的是建模思路,可能不会贴上代码。
② 以下出现的S都代表源点,T都代表汇点。
1、飞行员配对问题
Solution:
二分图匹配模板。可以用匈牙利算法,也可用最大流。
网络流的建模:
由S向左部图连容量为1的边,右部图向T连容量为1的边,左右部图间根据给出的条件,对应连上一条容量为1的边。
2、分配问题
二分图带权匹配模板。可以用KM算法,也可以用费用流。
费用流的建模:
在二分图最大匹配建图的基础上,给左右部图间的连边赋上1的费用,其他边费用为0。
3、试题库问题
二分图多重匹配模板。
网络流的建模:
在二分图最大匹配建图的基础上,只需要把T连向左部图(左部图为题中试题类型)的边容量改为需要的试题量。
4、圆桌问题
实际上可以说应该说是多重匹配问题。
这个合适上一个题的区别就在于,上一题只有左部图可以连多条边,而此题左右部图都可以。
所以建模的话,只需在上述基础上,右部图再往T连对应容量的边即可。
5、负载平衡问题
Solution:
这个题其实费用流才是真暴力。。。数学做法只要O(nlogn)。
但是,既然是24题当然得用网络流知识做嘛。。
这个建模没那么好想:
首先,我们需要注意到,一个点它只可以与前面的点或后面的点进行交换,且要么补给别人,要么接受别人补给。
对于这种只有出入两种情况的,一般启发着往拆点二分图上靠。
那么,左部图的点向右部图的点连的边表示左部给右部补给。
然后,可以把所有的点需要或多余的给计算出来。
对于一个点,如果它有多余,那么从它对应的左节点 向 相邻的两节点的右节点 连容量为inf,费用为1的边。
然后,S连向这些节点的左节点,容量为多余的量,费用为0。
对于一个点,如果它需要别人补给,那么向T连一条容量为需要的量的边。
但是还有一个很重要的点!!
那就是,对于有多余的点,还需向相邻节点的左节点连一条连容量为inf,费用为1的边!!!
这就相当于:这个点并不直接向两侧的点供应,而是把他们当做中转站而已。。
Code↓:
#include<bits/stdc++.h>
#define RG register
#define IL inline
using namespace std;
const int N=110;
const int inf=0x3f3f3f3f;
queue<int> q;
int n,S,T,cost,s[N],a[N],tot=1,pre[N<<2],vis[N<<2],dis[N<<2],head[N<<2],Minf[N<<2];
struct EDGE{int next,to,v,w;}e[N*N];
IL void make(int a,int b,int c,int d) {
e[++tot]=(EDGE){head[a],b,c,d},head[a]=tot;
e[++tot]=(EDGE){head[b],a,0,-d},head[b]=tot;
}
IL int spfa() {
RG int i,x,y;
while(!q.empty()) q.pop();
memset(vis,0,sizeof(vis));
memset(dis,0x3f,sizeof(dis));
q.push(S),vis[S]=1,dis[S]=0,Minf[S]=inf;
while(!q.empty()) {
x=q.front(),vis[x]=0,q.pop();
for(i=head[x];i;i=e[i].next) {
if(e[i].v&&dis[y=e[i].to]>dis[x]+e[i].w) {
dis[y]=dis[x]+e[i].w;
pre[y]=i,Minf[y]=min(Minf[x],e[i].v);
if(!vis[y]) vis[y]=1,q.push(y);
}
}
}
return dis[T]!=inf;
}
IL void update() {
RG int i,now=T;
while(now!=S) {
i=pre[now];
e[i].v-=Minf[T],e[i^1].v+=Minf[T];
now=e[i^1].to;
}
cost+=Minf[T]*dis[T];
}
int main()
{
RG int i,ave,sum=0;
scanf("%d",&n);
for(i=1;i<=n;++i) scanf("%d",&s[i]),sum+=s[i];
for(i=1,ave=sum/n;i<=n;++i) s[i]-=ave;
S=1,T=2*n+2;
for(i=1;i<=n;++i) {
if(s[i]>0) make(S,i+1,s[i],0);
else make(i+n+1,T,-s[i],0);
}
for(i=1;i<=n;++i) {
if(i!=1) make(i+1,i,inf,1),make(i+1,i+n,inf,1);
if(i!=n) make(i+1,i+2,inf,1),make(i+1,i+2+n,inf,1);
}
make(2,n+1,inf,1),make(2,n*2+1,inf,1);
make(n+1,2,inf,1),make(n+1,n+2,inf,1);
while(spfa()) update();
printf("%d
",cost);
return 0;
}
6、软件补丁问题
Solution:
最短路!!(雾)
一个需要注意的地方是,每个补丁可以用无限次。。。
又由于错误数很少,所以,可以直接状压一下表示当前错误状态,跑最短路即可
贴一下判断部分Code↓:
IL int judge(int x,int id) {
if((x&b1[id])!=b1[id]||(x&b2[id])) return -1;
x-=x&f1[id],x|=f2[id];
return x;
}
7、孤岛营救问题
Solution:
BFS!!(额额额)
思考一下如果需要知道什么?
拥有的钥匙情况!
不知道怎么办,设出来就好了。
钥匙数小于等于10,直接状压后BFS即可。
8、魔术球问题
Solution:
首先需要知道一个结论:
放的球数和柱子数成正相关
所以本题可以直接贪心(>_<)
但是网络流怎么样做呢??
还是考虑我们如果知道了什么东西就好做点了。
球数和柱子数是吧。
既然不知道,不妨尝试枚举,枚举基于上结论。
考虑新进来一个球。
那么由上结论可知,只要能放,就不会加入柱子。
怎样判断一个球能不能放才是重点,网络流。
我们考虑枚举之前能够和他连边的数,连上一条容量为1的边,
如果,我们在当前残量网络上能跑出一条为1的增广路,那么就可以放,否则加柱子。
Zeny神犇:这个题可以完全转换成最小路径覆盖啊。
I:呃呃。。。
Zeny神犇:你太呆了,这个题就相当于是给定了路径数求最大点数啊。
菜鸡在神犇面前,只适合sto orz
9、汽车加油行驶问题
Solution:
直接最短路就好了。
我写的spfa,而且是直接再网格上跑的,懒。
需要注意一下转移的时候,边权可能需要讨论一下。
10、最小路径覆盖问题
Solution:
路径最少意味着什么?
终点数最少。这又意味着什么?
没有出度的点最少。这在一张左右两部分别表示点入度和出度的二分图上又意味这什么?
左部表示出度,没出度的点最少,就是左部没被匹配的点最少,那么就是:
总点数-最大匹配。
所以直接最大流。
11、太空飞行计划问题
Solution:
最大权闭合子图模板。
闭合子图:
给定一个有向图,从中选择一些点组成一个点集V.对于V中任意一个点,其后续节点都仍然在V中.
最大权闭合子图的话,就是给一些边权值,有正有负,求权值最大的闭合子图。
给个结论:
最大权 闭合子图=正权之和-最小割/最大流
建模:
S向正权点连权值的边,负权点向T连权值绝对值的边,正权点负权点间建inf的边
可以发现,这个还是很好理解的:
S与正权点间的边如果满流了,那么意味着这些费用都用来填补负权了,没有收益。
否则,这个差值就代表着选这个点及其后继点的收益。
负权点与T同样可以类似分析。
思维还是很巧妙的。
12、方格取数问题
Solution:
首先,需要反向认识到:取数最大总和=所有数总和-不取的数最小的和
这应该联想到最小割,那么考虑怎么建图:
我们发现一个点能够影响的有且仅有它周围四个点,那么考虑黑白染色
假设对于每一个黑点,我们向它四周的几个点连边,容量为无限.
同时S向黑点连边,容量为黑点点权
对于白点,白点向T连边,容量为白点点权
这个时候跑最小割(最大流)即可
此时我们割掉的那些边必然是非无限边,所以割一条边可以代表着:不选对应的那个格子。