前言
网络流大法好
只刷了14道题目,剩下的真的做不动啦。
1.飞行员匹配问题
传送门
本题为二分图匹配问题,可以用网络流解决,也可以用匈牙利算法解决。
这题是一道SPJ,方案不唯一,样例好长时间不过,我以为我错了。。。
#include <cstdio>
#include <iostream>
#include <cstring>
#include <queue>
#define il inline
using namespace std;
const int inf=0x7fffffff;
const int maxm=1e7+1;
int head[maxm],to[maxm*2],cap[maxm*2],net[maxm*2];
int cnt=1;
il void add(int x,int y,int c){cnt++,to[cnt]=y,cap[cnt]=c,net[cnt]=head[x],head[x]=cnt;}
int flow[maxm],pre[maxm],id[maxm],match;
queue <int> dl;
il int BFS(int s,int t)
{
while(!dl.empty()) dl.pop();
memset(pre,-1,sizeof(pre));
pre[s]=0,flow[s]=inf;
dl.push(s);
while(!dl.empty())
{
int x=dl.front();
dl.pop();
if(x==t) break;
for(int i=head[x];i;i=net[i])
if(to[i]!=s&&cap[i]>0&&pre[to[i]]==-1)
{
pre[to[i]]=x,id[to[i]]=i;
flow[to[i]]=min(flow[x],cap[i]);
dl.push(to[i]);
}
}
if(pre[t]==-1) return -1;
else return flow[t];
}
il void change_cap(int s,int t,int x)
{
int now=t,last,sum=0;
while(now!=s)
{
cap[id[now]]-=x;
cap[id[now]^1]+=x;
now=pre[now];
//if((sum&1)==0) match[now]=last;
//else last=now;
//sum++;
}
}
il int maxflow(int s,int t)
{
int max_flow=0;
int d=0;
while((d=(BFS(s,t)))!=-1)
{
change_cap(s,t,d);
max_flow+=d;
}
return max_flow;
}
int main()
{
int n,m,s,t;
scanf("%d%d",&m,&n);
s=0,t=n+1;
int s1=0,s2=0;
while(1)
{
scanf("%d%d",&s1,&s2);
if(s1==-1&&s2==-1) break;
add(s1,s2,inf),add(s2,s1,0);
}
for(int i=1;i<=m;i++) add(s,i,1),add(i,s,0);
for(int i=m+1;i<=n;i++) add(i,t,1),add(t,i,0);
printf("%d\n",maxflow(s,t));
for(int i=2;i<=cnt;i+=2)
{
s1=to[i],s2=to[i^1];
if(s1==s||s1==t||s2==s||s2==t) continue;
if(cap[i^1]) printf("%d %d\n",s2,s1);
}
return 0;
}
2.软件补丁问题
传送门
神TM网络流。
SPFA+状压轻松水过
#include <cstdio>
#include <iostream>
#include <queue>
#include <cstring>
using namespace std;
bool vis[1<<21];
int inf;
int ok1[500],ok2[500],help[500],cas[500],time[500],dis[1<<21];
queue <int> dl;
int n,m;
char s[200];
int spfa()
{
memset(dis,127/3,sizeof(dis));
dis[(1<<n)-1]=0;
vis[(1<<n)-1]=1;
inf=dis[1];
dl.push((1<<n)-1);
while(!dl.empty())
{
int x=dl.front();
dl.pop();
vis[x]=0;
for(int i=1;i<=m;i++)
{
bool f1=((ok1[i]&x)==ok1[i]),f2=((ok2[i]&x)==0);
if(!f1||!f2) continue;
int to=x&(help[i]);
to|=cas[i];
if(dis[x]+time[i]<dis[to])
{
dis[to]=dis[x]+time[i];
if(vis[to]) continue;
vis[to]=0,dl.push(to);
}
}
}
return dis[0]==inf?0:dis[0];
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++)
{
scanf("%d %s",&time[i],s);
for(int j=0;j<strlen(s);j++)
{
if(s[j]=='+') ok1[i]|=(1<<j);
if(s[j]=='-') ok2[i]|=(1<<j);
}
scanf("%s",s);
int l=0,r=0;
for(int j=0;j<strlen(s);j++)
{
if(s[j]=='-') l+=(1<<j);
if(s[j]=='+') r+=(1<<j);
}
help[i]=~l,cas[i]=r;
}
//cout<<ok1[1]<<" "<<ok2[1]<<endl;
printf("%d\n",spfa());
return 0;
}
3.负载平衡问题
传送门
要不是这标签,打死我也不想最小费用最大流。
网络流之精髓,在于建模。
建模分析:
首先,我们先计算出平均的货物数量。
虚拟出一个源点和一个汇点。
将一个仓库拆成两个节点,供应节点X以及需求节点Y
从一个仓库的供应节点向相邻仓库的需求节点建立容量无限大,费用为1的边,模拟仓库之间的搬运。
从一个仓库的供应节点向相邻仓库的供应节点建立容量无限大,费用为1的边,模拟仓库之间的停留搬运
若一个仓库有盈余,则从源点向此仓库的供应节点建立容量为盈余,费用为0的边。
若一个仓库有亏损,则从此仓库的需求节点向汇点连一条容量为亏损,费用为0的边
在此网络上跑网络流,亏损和盈余之间会互补,此时求出的费用即为最小费用。
震惊的是:此题EK居然和Dinic一样快
#include <cstdio>
#include <iostream>
#include <cstring>
#include <queue>
#define il inline
using namespace std;
const int inf=0x7fffffff;
const int maxm=11000;
int head[maxm],to[maxm*2],cap[maxm*2],net[maxm*2],cost[maxm*2];
int val[maxm];
int cnt=1;
il void add(int x,int y,int c,int z){cnt++,to[cnt]=y,cost[cnt]=z,cap[cnt]=c,net[cnt]=head[x],head[x]=cnt;}
int flow[maxm],pre[maxm],id[maxm],dis[maxm],max_flow,min_cost;
bool vis[maxm];
queue <int> dl;
il bool BFS(int s,int t)
{
while(!dl.empty()) dl.pop();
memset(pre,-1,sizeof(pre));
memset(dis,127/3,sizeof(dis));
dis[s]=0,flow[s]=inf,pre[s]=0,vis[s]=1;
dl.push(s);
while(!dl.empty())
{
int x=dl.front();
dl.pop();
vis[x]=0;
for(int i=head[x];i;i=net[i])
{
int tmp=to[i];
if(cap[i]>0&&dis[tmp]>dis[x]+cost[i])
{
dis[tmp]=dis[x]+cost[i];
pre[tmp]=x;
id[tmp]=i;
flow[tmp]=min(flow[x],cap[i]);
if(!vis[tmp]) vis[tmp]=1,dl.push(tmp);
}
}
}
return pre[t]==-1?0:1;
}
il void change_cap(int s,int t,int x)
{
int now=t;
while(now!=s)
{
cap[id[now]]-=x,cap[id[now]^1]+=x;
now=pre[now];
}
}
void il get_ans(int s,int t)
{
max_flow=0,min_cost=0;
while(BFS(s,t))
{
//printf("%d\n",flow[t]);
max_flow+=flow[t],min_cost+=dis[t]*flow[t];
change_cap(s,t,flow[t]);
}
}
il void adx(int x,int y,int cax,int c)
{
add(x,y,cax,c),add(y,x,0,-c);
}
int main()
{
int n,sum=0,s=0,t=2000;
scanf("%d",&n);
for(int i=1;i<=n;i++)
scanf("%d",&val[i]),sum+=val[i];
sum/=n;
for(int i=1;i<=n;i++)
{
val[i]-=sum;
if(val[i]>0) add(s,i,val[i],0),add(i,s,0,0);
else add(i+n,t,-val[i],0),add(t,i+n,0,0);
}
for(int i=1;i<=n;i++)
{
if(i!=n)
adx(i,i+1,inf,1),adx(i,i+n+1,inf,1);
if(i!=1)
adx(i,i-1,inf,1),adx(i,i+n-1,inf,1);
}
adx(1,n,inf,1),adx(1,2*n,inf,1),adx(n,1,inf,1),adx(n,n+1,inf,1);
get_ans(s,t);
return printf("%d",min_cost)*0;
}
4.魔术球问题
emmm
好题,真的是好题。
类型:最小路径覆盖
QWQ
我们把柱子看做路径,然后把球成点。
枚举球的数量,看看这些球的最小覆盖路径是多少。
是否大于柱子。
建图:每增加一个新节点,枚举前面的节点看看是否为平方数,是就建边。
实现:枚举!
按说应该二分的,但是若我们进行二分,就得重新建图。
那么多数组初始化,肯定很慢的。
若我们枚举,每次多加一个球,那我们就可以在原来的残留网络上跑最大流啦,快的一批。
#include <cstdio>
#include <iostream>
#include <cstring>
#include <queue>
#include <cmath>
#define il inline
using namespace std;
const int inf=0x7fffffff;
const int maxm=1e5+1;
const int anx=5000;
int head[maxm],to[maxm*2],cap[maxm*2],net[maxm*2];
int top[maxm];
int cnt=1;
il void add(int x,int y,int c){cnt++,to[cnt]=y,cap[cnt]=c,net[cnt]=head[x],head[x]=cnt;}
int flow[maxm],pre[2*anx+1],id[maxm],preflow=0,anaa;
int ans[maxm];
queue <int> dl;
bool vis[maxm];
il int BFS(int s,int t)
{
while(!dl.empty()) dl.pop();
memset(pre,-1,sizeof(pre));
pre[s]=0,flow[s]=inf;
dl.push(s);
while(!dl.empty())
{
int x=dl.front();
dl.pop();
if(x==t) break;
for(int i=head[x];i;i=net[i])
if(to[i]!=s&&cap[i]>0&&pre[to[i]]==-1)
{
pre[to[i]]=x,id[to[i]]=i;
flow[to[i]]=min(flow[x],cap[i]);
dl.push(to[i]);
}
}
if(pre[t]==-1) return -1;
else return flow[t];
}
il void change_cap(int s,int t,int x)
{
int now=t,last,sum=0;
while(now!=s)
{
cap[id[now]]-=x;
cap[id[now]^1]+=x;
now=pre[now];
}
}
il void maxflow(int s,int t)
{
//int max_flow=preflow;
int d=0;
while((d=(BFS(s,t)))!=-1)
{
change_cap(s,t,d);
anaa-=d;
}
//return anaa;
}
inline void adx(int x,int y,int cax)
{
add(x,y,cax),add(y,x,0);
}
int main()
{
freopen("balla.in","r",stdin);
freopen("balla.out","w",stdout);
int nowball=0;
int n,s,t;
scanf("%d",&n);
s=0,t=10000;
while(1)
{
anaa++;
nowball++;
for(int i=1;i<nowball;i++)
if(sqrt(i+nowball)==(int)sqrt(i+nowball)) adx(i,nowball+anx,1);
adx(s,nowball,1),adx(nowball+anx,t,1);
maxflow(s,t);
if(anaa>n) break;
}
printf("%d",--nowball);
for(int i=1;i<=nowball;i++)
for(int j=head[i];j;j=net[j])
if(cap[j]==0) ans[i]=to[j]-anx;
puts("");
for(int i=1;i<=nowball;i++)
{
if(vis[i]) continue;
int now=i;
while(now!=-anx)
printf("%d ",now),vis[now]=1,now=ans[now];
puts("");
}
return 0;
}
5.餐巾
类型:最小费用最大流
建模分析:首先 将每天拆成两个点
X代表每天需要的毛巾
Y代表每天产生的旧毛巾
需要的毛巾
1.每天需要的毛巾可以直接来自于购买毛巾
因此从源点向X连一条容量为need,费用为购买费用的边
2.每天需要的毛巾可以来源于之前洗的旧毛巾
因此从d1天前和d2天前产生旧毛巾的点向本天需要的点连容量无限大,费用为清洗费用的点
3.从每天需要的点向汇点连一条容量为need,费用为0的边
旧毛巾
1.每天至少产生need条旧毛巾
因此从源点向本天旧毛巾点连一条容量为need,费用为0的点
2.上一天的旧毛巾可以不去清洗
因此从上一天的旧毛巾点向本天的旧毛巾点连一条容量为inf,费用为0的点
分析:因为其他边都为无穷边,所以我们最终跑出的最大流即为need之和,最小费用流为答案
CODE
题目传送门
#include <cstdio>
#include <iostream>
#include <cstring>
#include <queue>
#define il inline
#define ll long long
using namespace std;
const int inf=0x7fffffff;
const int maxm=11000;
int head[maxm],to[maxm*2],net[maxm*2];
ll cost[maxm*2],cap[maxm*2];
int cnt=1;
il void add(int x,int y,ll c,ll z){cnt++,to[cnt]=y,cost[cnt]=z,cap[cnt]=c,net[cnt]=head[x],head[x]=cnt;}
ll flow[maxm],dis[maxm],max_flow,min_cost;
int pre[maxm],id[maxm];
ll need[maxm];
bool vis[maxm];
queue <int> dl;
il bool BFS(ll s,ll t)
{
while(!dl.empty()) dl.pop();
for(int i=0;i<=4011;i++) pre[i]=-1,dis[i]=inf;
dis[s]=0,flow[s]=inf,pre[s]=0,vis[s]=1;
dl.push(s);
while(!dl.empty())
{
int x=dl.front();
dl.pop();
vis[x]=0;
for(int i=head[x];i;i=net[i])
{
int tmp=to[i];
if(cap[i]>0&&dis[tmp]>dis[x]+cost[i])
{
dis[tmp]=dis[x]+cost[i];
pre[tmp]=x;
id[tmp]=i;
flow[tmp]=min(flow[x],cap[i]);
if(!vis[tmp]) vis[tmp]=1,dl.push(tmp);
}
}
}
return pre[t]==-1?0:1;
}
il void change_cap(int s,int t,ll x)
{
int now=t;
while(now!=s)
{
cap[id[now]]-=x,cap[id[now]^1]+=x;
now=pre[now];
}
}
void il get_ans(int s,int t)
{
max_flow=0,min_cost=0;
while(BFS(s,t))
{
max_flow+=flow[t],min_cost+=dis[t]*flow[t];
change_cap(s,t,flow[t]);
}
}
il void adx(int x,int y,ll cax,ll c)
{
add(x,y,cax,c),add(y,x,0,-c);
}
il ll read()
{
ll x=0,w=1;char ch=0;
while(ch<'0'||ch>'9') {if(ch=='-') w=-1;ch=getchar();}
while(ch>='0'&&ch<='9') {x=(x<<3)+(x<<1)+ch-'0';ch=getchar();}
return x*w;
}
int main()
{
freopen("napkin.in","r",stdin);
freopen("napkin.out","w",stdout);
int n;
scanf("%d",&n);
for(int i=1;i<=n;i++)
need[i]=read();
ll p=read(),d1=read(),p1=read(),d2=read(),p2=read();
int s=4010,t=4011;
for(int i=1;i<=n;i++)
{
adx(s,i,need[i],p),adx(s,i+n,need[i],0);//i为需求毛巾数量,第一个模拟的是直接购买毛巾的数量,第二个为今日旧毛巾的数量
adx(i,t,need[i],0);
if(i+d1<=n) adx(i+n,i+d1,inf,p1);
if(i+d2<=n) adx(i+n,i+d2,inf,p2);//今日需求毛巾可以由之前的快洗和慢洗毛巾得来
if(i+1<=n) adx(i+n,i+1+n,inf,0);
}
get_ans(s,t);
return printf("%lld\n",min_cost)*0;
}
6.最小路径覆盖问题
问题:给你若干个点和边,问最少用几条路可以覆盖掉全图。
问题转化:网络流
题目传送门
分析:
我们首先将原图用n条路径覆盖,每条边只经过每个节点。
现在尽量合并更多的路径(即将两个路径通过一条边首尾相连)。
可以知道,每合并两条路径,图中的路径覆盖数就会减少1。
所以我们只需要利用网络流合并相关的路径即可。
答案求解:
首先将每个节点拆成(Xi,Yi)两个节点,建立源点和汇点,分别连接(S,Xi)和(Yi,T)。
然后对于每一条原图中的边,建立边(Xi,Yi)即可。
这样每一条增广路都只会经过2个节点(Xa,Yb),对应合并的两个节点。
由于每个节点至多与一个节点合并,故边(S,Xi)和(Yi,T)容量为1。
此时的最大流对应的就是最多可以合并的路径数。
至于输出路径嘛,这题是SPJ,瞎搞就行。
#include <cstdio>
#include <iostream>
#include <cstring>
#include <queue>
#include <cmath>
#define il inline
using namespace std;
const int inf=0x7fffffff;
const int maxm=1e5+1;
const int anx=150;
int head[maxm],to[maxm*2],cap[maxm*2],net[maxm*2];
int top[maxm];
int cnt=1;
il void add(int x,int y,int c){cnt++,to[cnt]=y,cap[cnt]=c,net[cnt]=head[x],head[x]=cnt;}
int flow[maxm],pre[1100],id[maxm],preflow=0,anaa;
int ans[maxm];
queue <int> dl;
bool vis[maxm];
il int BFS(int s,int t)
{
while(!dl.empty()) dl.pop();
memset(pre,-1,sizeof(pre));
pre[s]=0,flow[s]=inf;
dl.push(s);
while(!dl.empty())
{
int x=dl.front();
dl.pop();
if(x==t) break;
for(int i=head[x];i;i=net[i])
if(to[i]!=s&&cap[i]>0&&pre[to[i]]==-1)
{
pre[to[i]]=x,id[to[i]]=i;
flow[to[i]]=min(flow[x],cap[i]);
dl.push(to[i]);
}
}
if(pre[t]==-1) return -1;
else return flow[t];
}
il void change_cap(int s,int t,int x)
{
int now=t,last,sum=0;
while(now!=s)
{
cap[id[now]]-=x;
cap[id[now]^1]+=x;
now=pre[now];
}
}
il int maxflow(int s,int t)
{
int max_flow=0;
int d=0;
while((d=(BFS(s,t)))!=-1)
{
change_cap(s,t,d);
max_flow+=d;
}
return max_flow;
}
inline void adx(int x,int y,int cax)
{
add(x,y,cax),add(y,x,0);
}
int main()
{
int n,m,s=0,t;
scanf("%d%d",&n,&m);
t=1000;
for(int i=1;i<=n;i++) adx(s,i,1),adx(i+anx,t,1);
for(int i=1;i<=m;i++)
{
int a,b;
scanf("%d%d",&a,&b);
adx(a,b+anx,1);
}
int Ans=maxflow(s,t);
for(int i=1;i<=n;i++)
for(int j=head[i];j;j=net[j])
if(cap[j]==0) ans[i]=to[j]-anx;
for(int i=1;i<=n;i++)
{
if(vis[i]) continue;
int now=i;
while(now!=-anx)
printf("%d ",now),vis[now]=1,now=ans[now];
puts("");
}
return printf("%d\n",n-Ans)*0;
}
7.试题库问题
这道题基本上算是网络流最简单的题了。
首先建图(屁话
首先,对于每个题库可以向汇点连一条容量为需求的边。
对于每个题目,先从源点向每个题目连一条容量为1的边。
然后从每个题目向所属题库连容量为1的边。
然后,没了。。。
方案输出,找一下cap为0的就行了。
SPJ的评测
#include <cstdio>
#include <iostream>
#include <cstring>
#include <queue>
#include <cmath>
#include <vector>
#define il inline
using namespace std;
const int inf=0x7fffffff;
const int maxm=1e5+1;
const int anx=150;
int head[maxm],to[maxm*2],cap[maxm*2],net[maxm*2];
int top[maxm];
int cnt=1;
il void add(int x,int y,int c){cnt++,to[cnt]=y,cap[cnt]=c,net[cnt]=head[x],head[x]=cnt;}
int flow[maxm],pre[11000],id[maxm],preflow=0,anaa;
queue <int> dl;
bool vis[maxm];
vector <int> ans[1100];
il int BFS(int s,int t)
{
while(!dl.empty()) dl.pop();
memset(pre,-1,sizeof(pre));
pre[s]=0,flow[s]=inf;
dl.push(s);
while(!dl.empty())
{
int x=dl.front();
dl.pop();
if(x==t) break;
for(int i=head[x];i;i=net[i])
if(to[i]!=s&&cap[i]>0&&pre[to[i]]==-1)
{
pre[to[i]]=x,id[to[i]]=i;
flow[to[i]]=min(flow[x],cap[i]);
dl.push(to[i]);
}
}
if(pre[t]==-1) return -1;
else return flow[t];
}
il void change_cap(int s,int t,int x)
{
int now=t,last,sum=0;
while(now!=s)
{
cap[id[now]]-=x;
cap[id[now]^1]+=x;
now=pre[now];
}
}
il int maxflow(int s,int t)
{
int max_flow=0;
int d=0;
while((d=(BFS(s,t)))!=-1)
{
change_cap(s,t,d);
max_flow+=d;
}
return max_flow;
}
inline void adx(int x,int y,int cax)
{
add(x,y,cax),add(y,x,0);
}
int main()
{
int n,k;
scanf("%d%d",&k,&n);
int s=0,t=5000;
for(int i=1,x;i<=k;i++)
scanf("%d",&x),adx(i,t,x);
for(int i=1;i<=n;i++)
{
int p;
adx(s,i+n,1);
scanf("%d",&p);
for(int j=1,x;j<=p;j++)
scanf("%d",&x),adx(i+n,x,1);
}
maxflow(s,t);
//printf("This is ans\n");
for(int i=1;i<=n;i++)
{
for(int j=head[i+n];j;j=net[j])
if(cap[j]==0)
{
ans[to[j]].push_back(i);
break;
}
}
for(int i=1;i<=k;i++)
{
printf("%d:",i);
for(int j=0;j<ans[i].size();j++)
printf(" %d",ans[i][j]);
puts("");
}
return 0;
}
8.星际转移问题
类型:网络最大流
分析:
飞船对于不同的时间,航道不同,所以用时间建模。
不停的增加时间,用飞船运载乘客,当某一时刻图中的最大流超过了乘客数,即为最小的花费时间。
建模:
首先从源点向地球连一条inf的边
从月球向汇点连一条inf的边
由于乘客可以在空间站等待,所以从上个时刻空间站的点(包括地月)向本时刻连一条inf的边
从飞船的上一时刻位置向本时刻位置连一条p[i]的边
然后不停跑最大流就可以了。
问题 :不存在的情况,即无从地球通往月球的航道,并查集即可解决。
懒得搞了,天数>600时输出无解。。。
CODE
#include <cstdio>
#include <iostream>
#include <cstring>
#include <queue>
#include <cmath>
#include <vector>
#define il inline
using namespace std;
const int inf=0x7fffffff;
const int maxm=1e5+1;
const int anx=150;
int head[maxm],to[maxm*2],cap[maxm*2],net[maxm*2];
int p[maxm];
int cnt=1;
il void add(int x,int y,int c){cnt++,to[cnt]=y,cap[cnt]=c,net[cnt]=head[x],head[x]=cnt;}
int flow[maxm],pre[11000],id[maxm],preflow=0,anaa;
queue <int> dl;
bool vis[maxm];
int dx[30][100],len[30],n,m,k;;
il int BFS(int s,int t)
{
while(!dl.empty()) dl.pop();
memset(pre,-1,sizeof(pre));
pre[s]=0,flow[s]=inf;
dl.push(s);
while(!dl.empty())
{
int x=dl.front();
dl.pop();
if(x==t) break;
for(int i=head[x];i;i=net[i])
if(to[i]!=s&&cap[i]>0&&pre[to[i]]==-1)
{
pre[to[i]]=x,id[to[i]]=i;
flow[to[i]]=min(flow[x],cap[i]);
dl.push(to[i]);
}
}
if(pre[t]==-1) return -1;
else return flow[t];
}
il void change_cap(int s,int t,int x)
{
int now=t,last,sum=0;
while(now!=s)
{
cap[id[now]]-=x;
cap[id[now]^1]+=x;
now=pre[now];
}
}
il int maxflow(int s,int t)
{
int max_flow=preflow;
int d=0;
while((d=(BFS(s,t)))!=-1)
{
change_cap(s,t,d);
max_flow+=d;
}
return max_flow;
}
inline void adx(int x,int y,int cax)
{
add(x,y,cax),add(y,x,0);
}
void make_map(int day)
{
for(int i=1;i<=n;i++)
adx(i+(day-1)*n,i+day*n,inf);
for(int i=1;i<=m;i++)
{
int x=dx[i][(day-1)%len[i]];
int y=dx[i][day%len[i]];
adx((day-1)*n+x,day*n+y,p[i]);
}
}
int main()
{
scanf("%d%d%d",&n,&m,&k);
n+=2;
for(int i=1,size;i<=m;i++)
{
scanf("%d",&p[i]);
scanf("%d",&size);
len[i]=size;
for(int j=0;j<size;j++)
{
scanf("%d",&dx[i][j]);
dx[i][j]+=2;
}
}
int s=0,t=10000;
int day=0;
while(1)
{
adx(s,day*n+2,inf),adx(day*n+1,t,inf);
if(day>=1)
make_map(day);
preflow=maxflow(s,t);
if(preflow>=k)
{
printf("%d\n",day);
return 0;
}
day++;
if(day==600)
{
printf("0\n");
return 0;
}
}
}
9.运输问题
这题也太裸了。。。。
类型:费用流
建图:
从源点向仓库连容量为提供量,费用为0的边
从商店向汇点连容量为需求量,费用为0的边
从每个仓库向商店连容量inf,费用为运输费的边
第一问跑最小费用最大流
第二问跑最大费用最大流
啥,你问我咋跑最大费用,SPFA跑最长路就行啊(滑稽
题目链接
#include <cstdio>
#include <iostream>
#include <cstring>
#include <queue>
#define il inline
using namespace std;
const int inf=0x7fffffff;
const int maxm=11000;
int head[maxm],to[maxm*2],cap[maxm*2],net[maxm*2],cost[maxm*2];
int need[maxm],sup[maxm],wt[299][299];
int cnt=1;
il void add(int x,int y,int c,int z){cnt++,to[cnt]=y,cost[cnt]=z,cap[cnt]=c,net[cnt]=head[x],head[x]=cnt;}
int flow[maxm],pre[maxm],id[maxm],dis[maxm],max_flow,min_cost;
bool vis[maxm];
queue <int> dl;
il bool BFS(int s,int t)
{
while(!dl.empty()) dl.pop();
memset(pre,-1,sizeof(pre));
memset(dis,127/3,sizeof(dis));
dis[s]=0,flow[s]=inf,pre[s]=0,vis[s]=1;
dl.push(s);
while(!dl.empty())
{
int x=dl.front();
dl.pop();
vis[x]=0;
for(int i=head[x];i;i=net[i])
{
int tmp=to[i];
if(cap[i]>0&&dis[tmp]>dis[x]+cost[i])
{
dis[tmp]=dis[x]+cost[i];
pre[tmp]=x;
id[tmp]=i;
flow[tmp]=min(flow[x],cap[i]);
if(!vis[tmp]) vis[tmp]=1,dl.push(tmp);
}
}
}
return pre[t]==-1?0:1;
}
il void change_cap(int s,int t,int x)
{
int now=t;
while(now!=s)
{
cap[id[now]]-=x,cap[id[now]^1]+=x;
now=pre[now];
}
}
il bool BFS1(int s,int t)
{
while(!dl.empty()) dl.pop();
memset(pre,-1,sizeof(pre));
memset(dis,128,sizeof(dis));
dis[s]=0,flow[s]=inf,pre[s]=0,vis[s]=1;
dl.push(s);
while(!dl.empty())
{
int x=dl.front();
dl.pop();
vis[x]=0;
for(int i=head[x];i;i=net[i])
{
int tmp=to[i];
if(cap[i]>0&&dis[tmp]<dis[x]+cost[i])
{
dis[tmp]=dis[x]+cost[i];
pre[tmp]=x;
id[tmp]=i;
flow[tmp]=min(flow[x],cap[i]);
if(!vis[tmp]) vis[tmp]=1,dl.push(tmp);
}
}
}
return pre[t]==-1?0:1;
}
void il get_ans(int s,int t)
{
max_flow=0,min_cost=0;
while(BFS(s,t))
{
max_flow+=flow[t],min_cost+=dis[t]*flow[t];
change_cap(s,t,flow[t]);
}
}
void il get_ans1(int s,int t)
{
max_flow=0,min_cost=0;
while(BFS1(s,t))
{
max_flow+=flow[t],min_cost+=dis[t]*flow[t];
change_cap(s,t,flow[t]);
}
}
il void adx(int x,int y,int cax,int c)
{
add(x,y,cax,c),add(y,x,0,-c);
}
int main()
{
freopen("tran.in","r",stdin);
freopen("tran.out","w",stdout);
int n,m;
scanf("%d%d",&n,&m);
int s=0,t=m+n+1;
for(int i=1;i<=n;i++)
scanf("%d",&sup[i]),adx(s,i,sup[i],0);
for(int i=1;i<=m;i++)
scanf("%d",&need[i]),adx(i+n,t,need[i],0);
for(int i=1;i<=n;i++)
for(int j=1,x;j<=m;j++)
scanf("%d",&wt[i][j]),adx(i,j+n,inf,wt[i][j]);
get_ans(s,t);
printf("%d\n",min_cost);
memset(head,0,sizeof(head)),memset(to,0,sizeof(to));
memset(cap,0,sizeof(cap)),memset(cost,0,sizeof(cost));
cnt=1;
for(int i=1;i<=n;i++)
adx(s,i,sup[i],0);
for(int i=1;i<=m;i++)
adx(i+n,t,need[i],0);
for(int i=1;i<=n;i++)
for(int j=1,x;j<=m;j++)
adx(i,j+n,inf,wt[i][j]);
get_ans1(s,t);
return printf("%d\n",min_cost)*0;
}
10.航空问题
费用流问题。
建图:
源点向起点连inf费用为0的边
终点向汇点连inf费用为0的边
除了起点终点,每个点只能走一次。
所以拆点,容量为1,费用为1
起点终点容量为2,费用为1
输入的边容量为1费用为0
跑最大费用流
若最大流为2,则有解
因为起点终点经过两次 所以 答案-2
CODE
#include <cstdio>
#include <iostream>
#include <cstring>
#include <queue>
#include <map>
#define il inline
using namespace std;
const int inf=0x7fffffff;
const int maxm=11000;
int head[maxm],to[maxm*2],cap[maxm*2],net[maxm*2],cost[maxm*2];
map <string,int> idx;
string name[maxm];
int cnt=1;
il void add(int x,int y,int c,int z){cnt++,to[cnt]=y,cost[cnt]=z,cap[cnt]=c,net[cnt]=head[x],head[x]=cnt;}
int flow[maxm],pre[maxm],id[maxm],dis[maxm],max_flow,min_cost,ans[maxm],w[maxm];
bool vis[maxm];
queue <int> dl;
il void change_cap(int s,int t,int x)
{
int now=t;
while(now!=s)
{
cap[id[now]]-=x,cap[id[now]^1]+=x;
now=pre[now];
}
}
il bool BFS(int s,int t)
{
while(!dl.empty()) dl.pop();
memset(pre,-1,sizeof(pre));
memset(dis,128,sizeof(dis));
dis[s]=0,flow[s]=inf,pre[s]=0,vis[s]=1;
dl.push(s);
while(!dl.empty())
{
int x=dl.front();
dl.pop();
vis[x]=0;
for(int i=head[x];i;i=net[i])
{
int tmp=to[i];
if(cap[i]>0&&dis[tmp]<dis[x]+cost[i])
{
dis[tmp]=dis[x]+cost[i];
pre[tmp]=x;
id[tmp]=i;
flow[tmp]=min(flow[x],cap[i]);
if(!vis[tmp]) vis[tmp]=1,dl.push(tmp);
}
}
}
return pre[t]==-1?0:1;
}
void il get_ans(int s,int t)
{
max_flow=0,min_cost=0;
while(BFS(s,t))
{
max_flow+=flow[t],min_cost+=dis[t]*flow[t];
change_cap(s,t,flow[t]);
}
}
il void adx(int x,int y,int cax,int c)
{
add(x,y,cax,c),add(y,x,0,-c);
}
int main()
{
int n,v;
scanf("%d%d",&n,&v);
int s=0,t=1000;
for(int i=1;i<=n;i++)
{
cin>>name[i];
idx[name[i]]=i;
if(i!=1&&i!=n) adx(i,i+n,1,1);
else adx(i,i+n,2,1);
w[i]=cnt;
}
adx(s,1,inf,0);
adx(2*n,t,inf,0);
for(int i=1;i<=v;i++)
{
string s1,s2;
cin>>s1>>s2;
adx(idx[s1]+n,idx[s2],1,0);
}
get_ans(s,t);
if(max_flow!=2) printf("No Solution!\n");
else
{
memset(vis,0,sizeof(vis));
printf("%d\n",min_cost-2);
}
}
10.分配问题
类型:最小费用流
这题是裸题
qwq
#include <cstdio>
#include <iostream>
#include <cstring>
#include <queue>
#define il inline
using namespace std;
const int inf=0x7fffffff;
const int maxm=11000;
int head[maxm],to[maxm*2],cap[maxm*2],net[maxm*2],cost[maxm*2];
int need[maxm],sup[maxm],wt[299][299];
int cnt=1;
il void add(int x,int y,int c,int z){cnt++,to[cnt]=y,cost[cnt]=z,cap[cnt]=c,net[cnt]=head[x],head[x]=cnt;}
int flow[maxm],pre[maxm],id[maxm],dis[maxm],max_flow,min_cost;
bool vis[maxm];
queue <int> dl;
il bool BFS(int s,int t)
{
while(!dl.empty()) dl.pop();
memset(pre,-1,sizeof(pre));
memset(dis,127/3,sizeof(dis));
dis[s]=0,flow[s]=inf,pre[s]=0,vis[s]=1;
dl.push(s);
while(!dl.empty())
{
int x=dl.front();
dl.pop();
vis[x]=0;
for(int i=head[x];i;i=net[i])
{
int tmp=to[i];
if(cap[i]>0&&dis[tmp]>dis[x]+cost[i])
{
dis[tmp]=dis[x]+cost[i];
pre[tmp]=x;
id[tmp]=i;
flow[tmp]=min(flow[x],cap[i]);
if(!vis[tmp]) vis[tmp]=1,dl.push(tmp);
}
}
}
return pre[t]==-1?0:1;
}
il void change_cap(int s,int t,int x)
{
int now=t;
while(now!=s)
{
cap[id[now]]-=x,cap[id[now]^1]+=x;
now=pre[now];
}
}
il bool BFS1(int s,int t)
{
while(!dl.empty()) dl.pop();
memset(pre,-1,sizeof(pre));
memset(dis,128,sizeof(dis));
dis[s]=0,flow[s]=inf,pre[s]=0,vis[s]=1;
dl.push(s);
while(!dl.empty())
{
int x=dl.front();
dl.pop();
vis[x]=0;
for(int i=head[x];i;i=net[i])
{
int tmp=to[i];
if(cap[i]>0&&dis[tmp]<dis[x]+cost[i])
{
dis[tmp]=dis[x]+cost[i];
pre[tmp]=x;
id[tmp]=i;
flow[tmp]=min(flow[x],cap[i]);
if(!vis[tmp]) vis[tmp]=1,dl.push(tmp);
}
}
}
return pre[t]==-1?0:1;
}
void il get_ans(int s,int t)
{
max_flow=0,min_cost=0;
while(BFS(s,t))
{
max_flow+=flow[t],min_cost+=dis[t]*flow[t];
change_cap(s,t,flow[t]);
}
}
void il get_ans1(int s,int t)
{
max_flow=0,min_cost=0;
while(BFS1(s,t))
{
max_flow+=flow[t],min_cost+=dis[t]*flow[t];
change_cap(s,t,flow[t]);
}
}
il void adx(int x,int y,int cax,int c)
{
add(x,y,cax,c),add(y,x,0,-c);
}
int main()
{
int n;
scanf("%d",&n);
int s=0,t=1000;
for(int i=1;i<=n;i++)
{
adx(s,i,1,0);
adx(i+n,t,1,0);
for(int j=1;j<=n;j++)
scanf("%d",&wt[i][j]),adx(i,j+n,1,wt[i][j]);
}
get_ans(s,t);
printf("%d\n",min_cost);
memset(head,0,sizeof(head)),memset(to,0,sizeof(to));
memset(cap,0,sizeof(cap)),memset(cost,0,sizeof(cost));
cnt=1;
for(int i=1;i<=n;i++)
{
adx(s,i,1,0);
adx(i+n,t,1,0);
for(int j=1;j<=n;j++)
adx(i,j+n,1,wt[i][j]);
}
get_ans1(s,t);
printf("%d\n",min_cost);
return 0;
}
11.圆桌问题
类型:网络最大流
建模:
对于每组人员,从源点S向每组连一条容量为人员数量的边
对于每个桌子,向汇点T连一条容量为容纳人员数量的边
每组人员向每个桌子连一条容量为1的边即可。
Judge:跑出来的最大流是否等于总人数
方案:枚举即可
#include <cstdio>
#include <iostream>
#include <queue>
#include <cstring>
#define il inline
using namespace std;
const int inf=0x7fffffff;
const int maxm=110000;
int head[maxm],to[maxm*2],cap[maxm*2],net[maxm*2],deep[maxm],cnt=1;
il void add(int x,int y,int c){cnt++,to[cnt]=y,cap[cnt]=c,net[cnt]=head[x],head[x]=cnt;}
queue <int> dl;
il bool BFS(int s,int t)
{
while(!dl.empty()) dl.pop();
memset(deep,-1,sizeof(deep));
dl.push(s),deep[s]=0;
while(!dl.empty())
{
int x=dl.front();dl.pop();
for(int i=head[x];i;i=net[i])
if(cap[i]>0&&deep[to[i]]==-1)
dl.push(to[i]),deep[to[i]]=deep[x]+1;
}
return deep[t]==-1?0:1;
}
int dfs(int now,int flow,int t)
{
if(now==t) return flow;
int w,used=0;
for(int i=head[now];i;i=net[i])
{
int v=to[i];
if(deep[v]==deep[now]+1&&cap[i])
{
w=dfs(v,min(flow-used,cap[i]),t);
cap[i]-=w;
cap[i^1]+=w;
used+=w;
if(used==flow) return flow;
}
}
if(!used) deep[now]=-1;
return used;
}
il int dinic(int s,int t)
{
int maxflow=0;
while(BFS(s,t)) maxflow+=dfs(s,inf,t);
return maxflow;
}
inline void adx(int x,int y,int cax)
{
add(x,y,cax),add(y,x,0);
}
il int read()
{
int x=0,w=1;
char ch=0;
while(ch<'0'||ch>'9')
{
if(ch=='-') w=-1;
ch=getchar();
}
while(ch>='0'&&ch<='9')
x=(x<<3)+(x<<1)+ch-'0',ch=getchar();
return x*w;
}
int main()
{
//freopen("roundtable.in","r",stdin);
//freopen("roundtable.out","w",stdout);
int n,m;
n=read(),m=read();
int s=0,t=10000,sum=0;
for(int i=1,x;i<=n;i++)
{
x=read(),sum+=x;
adx(s,i,x);
}
for(int i=1,x;i<=m;i++)
{
x=read();
adx(i+n,t,x);
}
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
adx(i,j+n,1);
int ans=(dinic(s,t)==sum);
printf("%d\n",ans);
if(ans)
{
for(int i=1;i<=n;i++)
{
for(int j=head[i];j;j=net[j])
if(cap[j]==0) printf("%d ",to[j]-n);
puts("");
}
}
return 0;
}
12.最长不下降子序列问题
qwq
类型:最大不交叉路径->最大流
第一问 可以用 n^2的办法解决,同时求出以某个数字最作为结尾时最长的不下降最序列长度,len[i]。
第二问 题目要求每个数字只用一次,问题为最大不交叉路径
考虑通过第一问求出的数组来分层建图
首先每个数字只用一次,很明显要拆点
当一个位置的len[i]为第一问答案时,这个位置可以作为结尾,于是从源点连一条容量为inf的边
反之,当一个位置len[i]=1时,那么这个位置可以作为开头,于是从此点的出点向汇点连一条inf的边
对于
i<j并且val[i]<=val[j]len[i]==len[j]−1时
i
<
j
并
且
v
a
l
[
i
]
<=
v
a
l
[
j
]
l
e
n
[
i
]
==
l
e
n
[
j
]
−
1
时
,这两个点可以接在一起,于是从j的出点向i的入点建一条容量为1的边
跑最大流即为答案
第三问,将1和n的出点和入点的边容量改为inf即可
#include <cstdio>
#include <iostream>
#include <queue>
#include <cstring>
#define il inline
using namespace std;
const int inf=0x7fffffff;
const int maxm=110000;
int head[1100],to[maxm*2],cap[maxm*2],net[maxm*2],deep[1100],cnt=1;
il void add(int x,int y,int c){cnt++,to[cnt]=y,cap[cnt]=c,net[cnt]=head[x],head[x]=cnt;}
queue <int> dl;
int maxlen[maxm],val[maxm];
il bool BFS(int s,int t)
{
while(!dl.empty()) dl.pop();
memset(deep,-1,sizeof(deep));
dl.push(s),deep[s]=0;
while(!dl.empty())
{
int x=dl.front();dl.pop();
for(int i=head[x];i;i=net[i])
if(cap[i]>0&&deep[to[i]]==-1)
dl.push(to[i]),deep[to[i]]=deep[x]+1;
}
return deep[t]==-1?0:1;
}
int dfs(int now,int flow,int t)
{
if(now==t) return flow;
int w,used=0;
for(int i=head[now];i;i=net[i])
{
int v=to[i];
if(deep[v]==deep[now]+1&&cap[i])
{
w=dfs(v,min(flow-used,cap[i]),t);
cap[i]-=w;
cap[i^1]+=w;
used+=w;
if(used==flow) return flow;
}
}
if(!used) deep[now]=-1;
return used;
}
il int dinic(int s,int t)
{
int maxflow=0;
while(BFS(s,t)) maxflow+=dfs(s,inf,t);
return maxflow;
}
inline void adx(int x,int y,int cax)
{
add(x,y,cax),add(y,x,0);
}
il int read()
{
int x=0,w=1;
char ch=0;
while(ch<'0'||ch>'9')
{
if(ch=='-') w=-1;
ch=getchar();
}
while(ch>='0'&&ch<='9')
x=(x<<3)+(x<<1)+ch-'0',ch=getchar();
return x*w;
}
int main()
{
freopen("alis.in","r",stdin);
freopen("alis.out","w",stdout);
int n=read();
for(int i=1;i<=n;i++)
val[i]=read();
for(int i=1;i<=n;i++)
maxlen[i]=1;
int ans=1;
for(int i=2;i<=n;i++)
for(int j=1;j<i;j++)
if(val[j]<=val[i]) maxlen[i]=max(maxlen[i],maxlen[j]+1),ans=max(ans,maxlen[i]);
printf("%d\n",ans);
if(ans==1) printf("%d\n%d\n",n,n);
if(ans==0) printf("0\n0\n");
if(ans!=0&&ans!=1)
{
int s=0,t=1001;
for(int i=1;i<=n;i++)
{
adx(i,i+n,1);
if(maxlen[i]==ans) adx(s,i,inf);
if(maxlen[i]==1) adx(i+n,t,inf);
for(int j=1;j<i;j++)
if(val[j]<=val[i]&&maxlen[j]+1==maxlen[i]) adx(i+n,j,1);
}
printf("%d\n",dinic(s,t));
memset(head,0,sizeof(head)),cnt=1;
for(int i=1;i<=n;i++)
{
if(i!=1&&i!=n) adx(i,i+n,1);
else adx(i,i+n,inf);
if(maxlen[i]==ans) adx(s,i,inf);
if(maxlen[i]==1) adx(i+n,t,inf);
for(int j=1;j<i;j++)
if(val[j]<=val[i]&&maxlen[j]+1==maxlen[i]) adx(i+n,j,1);
}
printf("%d\n",dinic(s,t));
}
return 0;
}
13.孤岛营救问题
类型:状压+BFS
不是网络流(滑稽
很明显是个状压嘛。
我们状态压缩钥匙。
vis[x][y][S]
v
i
s
[
x
]
[
y
]
[
S
]
表示在x y这个位置持有钥匙状态为S是否来过
BFS队列里存4个变量
分别为当前横纵坐标,步数以及钥匙状态
map[x1][y1][x2][y2]表示两个点直接是否有墙或者是门。
pas[x][y][i]表示x y这个点放的钥匙的种类
num[x][y]表示当前点钥匙数目
坑点:
1.钥匙不是用了就没了((#`O′)喂,没了才有问题吧
2.一个点可以放多个钥匙(人家又没说只放一个。
3.初始点可以放钥匙
4.别忘了输出-1….
虽然我只被2坑了
CODE
题目传送门
#include <cstdio>
#include <iostream>
#include <queue>
#define il inline
using namespace std;
bool vis[11][11][(1<<11)];
int map[11][11][11][11];
int pas[11][11][11],num[11][11];
struct node{
int x,y,step,cos;
};
queue <node> dl;
int n,m,p,k;
int dx[]={0,1,-1,0,0},dy[]={0,0,0,1,-1};
il int BFS()
{
int nows=0;
for(int i=1;i<=num[1][1];i++)
nows|=(1<<(pas[1][1][i]-1));
vis[1][1][nows]=1;
dl.push((node){1,1,0,nows});
while(!dl.empty())
{
node now=dl.front();
dl.pop();
if(now.x==n&&now.y==m)
return now.step;
for(int i=1;i<=4;i++)
{
int xx=now.x+dx[i],yy=now.y+dy[i],t;
if(xx<1||yy<1||xx>n||yy>m) continue;
if(map[now.x][now.y][xx][yy]==-1) continue;
if((t=map[now.x][now.y][xx][yy])!=0)
if((now.cos&(1<<(t-1)))==0) continue;
int cosx=now.cos;
for(int j=1;j<=num[xx][yy];j++)
cosx|=(1<<(pas[xx][yy][j]-1));
if(vis[xx][yy][cosx]) continue;
vis[xx][yy][cosx]=1;
dl.push((node){xx,yy,now.step+1,cosx});
}
}
return -1;
}
int main()
{
//freopen("rescue.in","r",stdin);
//freopen("rescue.out","w",stdout);
scanf("%d%d%d%d",&n,&m,&p,&k);
for(int i=1;i<=k;i++)
{
int x1,x2,y1,y2,g;
scanf("%d%d%d%d%d",&x1,&y1,&x2,&y2,&g);
if(g==0) map[x1][y1][x2][y2]=map[x2][y2][x1][y1]=-1;
else map[x1][y1][x2][y2]=map[x2][y2][x1][y1]=g;
}
int s;
scanf("%d",&s);
for(int i=1;i<=s;i++)
{
int x,y,p;
scanf("%d%d%d",&x,&y,&p);
pas[x][y][++num[x][y]]=p;
}
printf("%d\n",BFS());
return 0;
}
14.汽车行驶问题
按照要求BFS就行了,
坑点:强制消费。
qwq
#include <cstdio>
#include <iostream>
#include <queue>
#include <cstring>
#define il inline
using namespace std;
int vis[110][110][12],n,a,b,c,k;
struct node{
int x,y,cost,oil;
};
queue <node> dl;
bool map[110][110];
int dx[]={0,1,-1,0,0},dy[]={0,0,0,1,-1};
il int BFS()
{
memset(vis,127/3,sizeof(vis));
dl.push((node){1,1,0,k});
vis[1][1][k]=0;
while(!dl.empty())
{
node now=dl.front();
//printf("%d %d %d %d\n",now.x,now.y,now.cost,now.oil);
node nex;
dl.pop();
if(now.oil==0) continue;
//printf("1\n");
for(int i=1;i<=4;i++)
{
int xx=now.x+dx[i],yy=now.y+dy[i];
if(xx<1||yy<1||xx>n||yy>n) continue;
nex.x=xx,nex.y=yy,nex.cost=now.cost,nex.oil=now.oil-1;
if(nex.x<now.x||nex.y<now.y) nex.cost+=b;
if(map[nex.x][nex.y])
nex.oil=k,nex.cost+=a;
if(vis[nex.x][nex.y][nex.oil]>nex.cost) vis[nex.x][nex.y][nex.oil]=nex.cost,dl.push(nex);
if(map[nex.x][nex.y]==0)
{
nex.oil=k,nex.cost+=(a+c);
if(vis[nex.x][nex.y][nex.oil]>nex.cost) vis[nex.x][nex.y][nex.oil]=nex.cost,dl.push(nex);
}
}
}
int ans=vis[0][0][0];
for(int i=0;i<=k;i++)
ans=min(ans,vis[n][n][i]);
return ans;
}
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",&map[i][j]);
return printf("%d\n",BFS())*0;
}
15.太空飞行计划问题
http://blog.csdn.net/qq_35914587/article/details/79155879
忘了有这个分类了,写完了才想起来,不粘贴了。
16.方格取数问题
http://blog.csdn.net/qq_35914587/article/details/79163024