P1343 地震逃生
_Fontainebleau_ · · 题解
这一题一看就是一道网络流最大流。
逃生道路——>水管
学生——>水
教室——>源点
安全地带——>汇点
最大流的精髓就在反向边 (・◇・)?
基本概念
残量=容量-已流过的量
反向边的流量值=正向流过的总流量,也就是说正向流过多少,反向可以流回多少。
最大流有好几种做法:
然额,窝只会
因为,窝是自学
#include<bits/stdc++.h>
using namespace std;
const int inf=2147483647;//inf:最大值
int cnt=1,head[13000];//cnt:第CNT条边head[i]:第i个点属于第几条边
int n,m,s,t;//n个点m条边s:源点t:汇点
inline int Read()
{
int x=0;
char c=getchar();
while(c>'9'||c<'0')c=getchar();
while(c>='0'&&c<='9')x=x*10+c-'0',c=getchar();
return x;
}
struct Node
{
int v;//当前点
int next;//连接点
int val;//容量
}node[250000];//node[i]:第i条边的情况
inline void addedge(int u,int v,int val)
{
node[++cnt].v=v;
node[cnt].val=val;
node[cnt].next=head[u];
head[u]=cnt;
}
int dep[13000],gap[13000];//dep[i]表示节点i的深度,gap[i]表示深度为i的点的数量
void bfs()//倒着搜
{
memset(dep,-1,sizeof(dep));//把深度变为-1(0会导致gap崩坏)
memset(gap,0,sizeof(gap));
dep[t]=0;//汇点深度为0
gap[0]=1;//深度为0的点有1个
queue<int>q;
q.push(t);//t点入栈
while(!q.empty())
{
int u=q.front();
q.pop();
for(int i=head[u];i;i=node[i].next)//head[u]:u点所在的边,node[i].next:u点所在的边的下一个点,就这样遍历下去
{
int v=node[i].v;//v为当前边的下一个点
if(dep[v]!=-1) continue;//dep[v]!=-1相当于v点已被遍历||不管
q.push(v);
dep[v]=dep[u]+1;//v点的深度比u点大1
gap[dep[v]]++;
}//直到所有点都被遍历过
}
return;
}//从t到s跑一遍bfs,标记深度
int maxflow;
int dfs(int u,int flow)
{
if(u==t)
{
maxflow+=flow;
return flow;
}
int used=0;
for(int i=head[u];i;i=node[i].next)//head[u]:u点所在的边,node[i].next:u点所在的边的下一个点,就这样遍历下去
{
int d=node[i].v;
if(node[i].val&&dep[d]+1==dep[u])//如果这条边的残量大于0,且没有断层
{
int mi=dfs(d,min(node[i].val,flow-used));//流量
if(mi)
{
node[i].val-=mi;//正向边-mi
node[i^1].val+=mi;//反向边+mi
used+=mi;
}
if(used==flow)return used;
}
}
//如果已经到了这里,说明该点出去的所有点都已经流过了
//并且从前面点传过来的流量还有剩余
//则此时,要对该点更改dep
//使得该点与该点出去的点分隔开
--gap[dep[u]];
if(gap[dep[u]]==0)dep[s]=n+1;//出现断层,无法到达t了
dep[u]++;//层++
gap[dep[u]]++;//层数对应个数++
return used;
}
int ISAP()
{
maxflow=0;
bfs();//从t到s跑一遍bfs,标记深度
while(dep[s]<n) dfs(s,inf);//每走一遍增广路,s的层数会加1,如果一直没有出现断层,最多跑n-dep(刚bfs完时s的深度)条增广路共有n个点
return maxflow;
}
int main()
{
n=Read(),m=Read(),s=Read(),t=Read();
int u,v,w;
for(int i=1;i<=m;i++)
{
u=Read();
v=Read();
w=Read();
addedge(u,v,w);//正向边
addedge(v,u,0);//反向边
}
printf("%d\n",ISAP());
return 0;
}
但是,
int al=ISAP(),time;
if(al)
{
time=x/al;
if(al*time<x) time++;
printf("%d %d\n",al,time);
}
else
printf( "Orz Ni Jinan Saint Cow!\n");
return 0;
附上
#include<bits/stdc++.h>
#define MAXN 205
#define MAXM 4005
using namespace std;
const int inf=2147483647;//inf:最大值
int cnt=1,head[MAXN];//cnt:第CNT条边head[i]:第i个点属于第几条边
int n,m,s,t;//n个点m条边s:源点t:汇点
inline int Read()
{
int x=0;
char c=getchar();
while(c>'9'||c<'0')c=getchar();
while(c>='0'&&c<='9')x=x*10+c-'0',c=getchar();
return x;
}
struct Node
{
int v;//当前点
int next;//连接点
int val;//容量
}node[MAXM];//node[i]:第i条边的情况
inline void addedge(int u,int v,int val)
{
node[++cnt].v=v;
node[cnt].val=val;
node[cnt].next=head[u];
head[u]=cnt;
}
int dep[MAXN],gap[MAXN];//dep[i]表示节点i的深度,gap[i]表示深度为i的点的数量
void bfs()//倒着搜
{
memset(dep,-1,sizeof(dep));//把深度变为-1(0会导致gap崩坏)
memset(gap,0,sizeof(gap));
dep[t]=0;//汇点深度为0
gap[0]=1;//深度为0的点有1个
queue<int>q;
q.push(t);//t点入栈
while(!q.empty())
{
int u=q.front();
q.pop();
for(int i=head[u];i;i=node[i].next)//head[u]:u点所在的边,node[i].next:u点所在的边的下一个点,就这样遍历下去
{
int v=node[i].v;//v为当前边的下一个点
if(dep[v]!=-1) continue;//dep[v]!=-1相当于v点已被遍历||不管
q.push(v);
dep[v]=dep[u]+1;//v点的深度比u点大1
gap[dep[v]]++;
}//直到所有点都被遍历过
}
return;
}//从t到s跑一遍bfs,标记深度
int maxflow;
int dfs(int u,int flow)
{
if(u==t)
{
maxflow+=flow;
return flow;
}
int used=0;
for(int i=head[u];i;i=node[i].next)//head[u]:u点所在的边,node[i].next:u点所在的边的下一个点,就这样遍历下去
{
int d=node[i].v;
if(node[i].val&&dep[d]+1==dep[u])//如果这条边的残量大于0,且没有断层
{
int mi=dfs(d,min(node[i].val,flow-used));//流量
if(mi)
{
node[i].val-=mi;//正向边-mi
node[i^1].val+=mi;//反向边+mi
used+=mi;
}
if(used==flow)return used;
}
}
//如果已经到了这里,说明该点出去的所有点都已经流过了
//并且从前面点传过来的流量还有剩余
//则此时,要对该点更改dep
//使得该点与该点出去的点分隔开
--gap[dep[u]];
if(gap[dep[u]]==0)dep[s]=n+1;//出现断层,无法到达t了
dep[u]++;//层++
gap[dep[u]]++;//层数对应个数++
return used;
}
int ISAP()
{
maxflow=0;
bfs();//从t到s跑一遍bfs,标记深度
while(dep[s]<n) dfs(s,inf);//每走一遍增广路,s的层数会加1,如果一直没有出现断层,最多跑n-dep(刚bfs完时s的深度)条增广路共有n个点
return maxflow;
}
int main()
{
n=Read(),m=Read(),s=1,t=n;
int x=Read();
int u,v,w;
for(int i=1;i<=m;i++)
{
u=Read();
v=Read();
w=Read();
addedge(u,v,w);//正向边
addedge(v,u,0);//反向边
}
int al=ISAP(),time;
if(al)
{
time=x/al;
if(al*time<x) time++;
printf("%d %d\n",al,time);
}
else
printf( "Orz Ni Jinan Saint Cow!\n");
return 0;
}
完结✿✿ヽ(°▽°)ノ✿
update 2020.4.2
新增一种算法as we know,
Edmonds-Karps增广路算法的时间复杂度为O(nm^2)。
然而在实际运用中则远远达不到这个界,
效率较高,一般能处理10^3~10^4规模的网络
——李煜东《算法竞赛进阶指南》
先贴模板
#include<bits/stdc++.h>
#define mst(a,b) memset(a,b,sizeof(a))
#define For(i,k,j) for(register int i=(k);i<=(j);i++)
#define INF 2147483647
using namespace std;
const int MAXN=10001;
const int MAXM=100001;
int g[MAXN][MAXN]; //g[u][v] : u -> v 还可以流的量 (正向图)
int pre[MAXN]; // 记录每个点的前驱
bool vis[MAXN]; // 记录每个点是否被访问过
int n,m;
inline bool bfs(int s,int t)//经典BFS
{
//新的开始
mst(pre,-1);//重新计算pre[]
mst(vis,0);//重新记录vis[]
queue<int>q;//新建队列
vis[s]=true;//源点已被遍历
q.push(s);//压入队列
while(!q.empty())
{
int now=q.front();
q.pop();
For(i,1,n)
{
if(!vis[i]&&g[now][i]>0)//如果这个点没被遍历到,且now和i有管道
{
vis[i]=true;//当前点已被访问
pre[i]=now;//记录i点的前一个点是now
if(i==t)//如果i点是汇点,那么,一条路径产生
return 1;
q.push(i);//else,继续搜
}
}
}
return 0;//所有路径都被遍历完了
}
inline int EK(int s,int t)
{
int v,u,d,maxflow=0;
while(bfs(s,t))
{ //可以增广
v=t,d=INF;//找可增量d ,d为当前流量
while(v!=s)//回溯
{
u=pre[v]; // u记录v的前驱
d=min(d,g[u][v]); // d 和当前边(正向边)还可以流过去的量取最小值->能流多少
v=u;//v=前一个点
}
maxflow+=d;//最大流量
v=t;//Do it again
while(v!=s)//回溯
{
u=pre[v];// u记录v的前驱
g[u][v]-=d; //网络中的正向边剩下可以流的量减少d
g[v][u]+=d; //网络中的反向边剩下可以流的量增加d
v=u;
}
}
return maxflow;
}
int main()
{
scanf("%d%d",&n,&m);//n为点数,m为边数
int s,t;//源点&汇点
scanf("%d%d",&s,&t);
For(i,1,m)
{
int u,v,w;//起点,终点,管道流量
scanf("%d%d%d",&u,&v,&w);
g[u][v]+=w;//建图
}
printf("%d\n",EK(s,t));
return 0;
}
大家可以先看注释,用的是邻接矩阵。
这里就将详细讲一个部分
if(!vis[i]&&g[now][i]>0)//如果这个点没被遍历到,且now和i有管道
#include<bits/stdc++.h>
#define mst(a,b) memset(a,b,sizeof(a))
#define For(i,k,j) for(register int i=(k);i<=(j);i++)
#define INF 2147483647
using namespace std;
const int MAXN=10001;
const int MAXM=100001;
int g[MAXN][MAXN]; //g[u][v] : u -> v 还可以流的量 (正向图)
int pre[MAXN]; // 记录每个点的前驱
bool vis[MAXN]; // 记录每个点是否被访问过
int n,m;
bool bfs(int s,int t)//经典BFS
{
//新的开始
mst(pre,-1);//重新计算pre[]
mst(vis,0);//重新记录vis[]
queue<int>q;//新建队列
vis[s]=true;//源点已被遍历
q.push(s);//压入队列
while(!q.empty())
{
int now=q.front();
q.pop();
For(i,1,n)
{
if(!vis[i]&&g[now][i]>0)//如果这个点没被遍历到,且now和i有管道
{
vis[i]=true;//当前点已被访问
pre[i]=now;//记录i点的前一个点是now
if(i==t)//如果i点是汇点,那么,一条路径产生
return 1;
q.push(i);//else,继续搜
}
}
}
return 0;//所有路径都被遍历完了
}
inline int EK(int s,int t)
{
int v,u,d,maxflow=0;
while(bfs(s,t))
{ //可以增广
v=t,d=INF;//找可增量d ,d为当前流量
while(v!=s)//回溯
{
u=pre[v]; // u记录v的前驱
d=min(d,g[u][v]); // d 和当前边(正向边)还可以流过去的量取最小值->能流多少
v=u;//v=前一个点
}
maxflow+=d;//最大流量
v=t;//Do it again
while(v!=s)//回溯
{
u=pre[v];// u记录v的前驱
g[u][v]-=d; //网络中的正向边剩下可以流的量减少d
g[v][u]+=d; //网络中的反向边剩下可以流的量增加d
v=u;
}
}
return maxflow;
}
int main()
{
scanf("%d%d",&n,&m);//n为点数,m为边数
int s=1,t=n;//源点&汇点
int x;
scanf("%d",&x);
For(i,1,m)
{
int u,v,w;//起点,终点,管道流量
scanf("%d%d%d",&u,&v,&w);
g[u][v]+=w;//建图
}
int al=EK(s,t),time;
if(al)
{
time=x/al;
if(al*time<x) time++;
printf("%d %d\n",al,time);
}
else
printf( "Orz Ni Jinan Saint Cow!\n");
return 0;
}