这里全是板子。。。
先来一个模板题压压惊
先来一个EK算法(有些解释放在注释里了)
利用bfs实现,时间复杂度O(V*E^2)≈ FF;
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const ll N=101000;
#define endl '\n'
const ll inf=1<<30;
struct ty{
ll t,l,next;
}edge[N<<1];
ll cnt=1;//后面要让第i条边^1后能得到它的反向边(与它相邻)
ll head[N];
inline ll 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;
}
void add(ll a,ll b,ll c)
{
edge[++cnt].t=b;
edge[cnt].l=c;
edge[cnt].next=head[a];
head[a]=cnt;
}
ll inque[N];
struct Pre
{
ll v;//前一个节点
ll edge;//前一条边
}pre[N];
ll n,m,s,t;
ll a,b,c;
bool bfs()//点到终点是否还有路可以走
{
queue<ll> q;
memset(inque,0,sizeof inque);
memset(pre,-1,sizeof pre);
inque[s]=1;//标记起点
q.push(s);
while(!q.empty())
{
ll ty=q.front();
q.pop();
for(int i=head[ty];i!=-1;i=edge[i].next)
{
ll y=edge[i].t;
if(edge[i].l==0) continue;
if(inque[y]++) continue;//如果要这么写的话,这句话要放在上一句下面
//因为只有边大于0才有资格被加入
pre[y].v=ty;
pre[y].edge=i;
if(y==t) return 1;
//inque[y]++;//只有
q.push(y);
}
}
return 0;
}
ll ek()
{
ll ans=0;
while(bfs())//起点到终点还有路可以走
{
ll mi=inf;
for(int i=t;i!=s;i=pre[i].v)//从终点回溯
{
mi=min(mi,edge[pre[i].edge].l);//找最小边
}
for(int i=t;i!=s;i=pre[i].v)
{
edge[pre[i].edge].l-=mi;
edge[pre[i].edge^1].l+=mi;
}
ans+=mi;
}
return ans;
}
int main()
{
memset(head,-1,sizeof head);
n=read();m=read();//s=read();t=read();
s=1;t=m;
for(int i=1;i<=n;++i)
{
a=read();b=read();c=read();
add(a,b,c);
add(b,a,0);//添加反向边
}
cout<<ek()<<endl;
return 0;
}
Dinic:
相比于上面算法的O(nm^2)(n为点数,m为边数),Dinic可以达到O(n^2m),在稠密图上(比如二分匹配之类的)Dinic的优势就非常明显了。
(Dinic在跑二分图匹配时比匈牙利快很多。)
下面这个为加了当前弧优化的版本
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const ll N=101000;
#define endl '\n'
const ll inf=1<<30;
struct ty{
ll t,l,next;
}edge[N<<1];
ll cnt=1;//后面要让第i条边^1后能得到它的反向边(与它相邻)
ll head[N];
inline ll 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;
}
void add(ll a,ll b,ll c)
{
edge[++cnt].t=b;
edge[cnt].l=c;
edge[cnt].next=head[a];
head[a]=cnt;
}
ll inque[N];
ll dep[N];//用于分层
ll cur[N];//记录弧
ll vis;//这次是否能到达终点
ll ans=0;
struct Pre
{
ll v;//前一个节点
ll edge;//前一条边
}pre[N];
ll n,m,s,t;
ll a,b,c;
bool bfs()//用于分层
{
// memset(inque,0,sizeof inque);
// memset(dep,0x3f,sizeof dep);
for(int i=0;i<=n;++i)
{
cur[i]=head[i];
dep[i]=0x3f3f3f3f3f3f3f3f;
inque[i]=0;
}
dep[s]=0;//起点
queue<ll> q;
q.push(s);
while(!q.empty())
{
ll ty=q.front();
q.pop();
inque[ty]=0;//
for(int i=head[ty];i!=-1;i=edge[i].next)
{
ll y=edge[i].t;
if(dep[y]<=dep[ty]+1) continue;//无需更新
if(edge[i].l==0) continue;
dep[y]=dep[ty]+1;
if(inque[y]) continue;//看是否在队里
inque[y]++;
q.push(y);
}
}
if(dep[t]!=0x3f3f3f3f3f3f3f3f) return 1;
return 0;
}
ll dfs(ll u,ll flow)//节点,当前最小流量
{
ll rl=0;
if(u==t)
{
vis=1;
ans+=flow;
return flow;
}
ll used=0;//该点用过的流量
for(int i=cur[u]/*是cur!这里不是head(当前弧优化)*/;i!=-1;i=edge[i].next)
{
cur[u]=i;//修改当前弧
ll y=edge[i].t;
if(dep[y]==dep[u]+1&&edge[i].l)
{
rl=dfs(y,min(flow-used,edge[i].l));//减去后的费用拿去比较
if(rl==0) continue;
used+=rl;
edge[i].l-=rl;
edge[i^1].l+=rl;
if(used==flow) break;//满了
//return rl;
}
}
return used;
}
ll dic()
{
while(bfs())
{
vis=1;
while(vis)
{
vis=0;
dfs(s,inf);
}
}
return ans;
}
int main()
{
memset(head,-1,sizeof head);
n=read();m=read();s=read();t=read();
for(int i=1;i<=m;++i)
{
a=read();b=read();c=read();
add(a,b,c);
add(b,a,0);//添加反向边
}
cout<<dic()<<endl;
return 0;
}
同样是你谷那道板子题
可以看看两者的差距
(EK还吸了氧)
还有重量级的ISAP
通过统计每一层的节点数以及断层处理,做到只用跑一次bfs
时间复杂度O(n^2m);
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const ll N=101000;
#define endl '\n'
const ll inf=1<<30;
struct ty{
ll t,l,next;
}edge[N<<1];
ll cnt=1;//后面要让第i条边^1后能得到它的反向边(与它相邻)
ll head[N];
inline ll 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;
}
void add(ll a,ll b,ll c)
{
edge[++cnt].t=b;
edge[cnt].l=c;
edge[cnt].next=head[a];
head[a]=cnt;
}
ll inque[N];
ll dep[N];//用于分层
ll gap[N];//该层深度的点有多少个
ll cur[N];//记录弧
ll vis;//这次是否能到达终点
ll ans=0;
ll n,m,s,t;
ll a,b,c;
void bfs()//倒着跑一遍,标记深度
{
memset(dep,-1,sizeof dep);
memset(gap,0,sizeof gap);
dep[t]=0;//从终点往起点推
gap[0]=1;
queue<ll> q;
q.push(t);
while(!q.empty())
{
ll ty=q.front();
q.pop();
for(int i=head[ty];i!=-1;i=edge[i].next)
{
ll y=edge[i].t;
if(dep[y]!=-1) continue;//代表它被访问过了
q.push(y);
dep[y]=dep[ty]+1;
gap[dep[y]]++;
}
}
}
ll dfs(ll u,ll flow)
{
if(u==t)
{
ans+=flow;
return flow;
}
ll used=0;
for(int i=head[u];i!=-1;i=edge[i].next)
{
ll y=edge[i].t;
if(edge[i].l&&dep[y]+1==dep[u])//这里顺序是反的//
{
ll fl=dfs(y,min(edge[i].l,flow-used));
if(fl)
{
edge[i].l-=fl;
edge[i^1].l+=fl;
used+=fl;
}
if(used==flow) return used;//前面送来的都用完了
}
}
//否则:
gap[dep[u]]--;
if(!gap[dep[u]]) dep[s]=n+1;
dep[u]++;
gap[dep[u]]++;
return used;
}
ll isap()
{
ans=0;
bfs();
while(dep[s]<n) dfs(s,inf);
return ans;
}
int main()
{
memset(head,-1,sizeof head);
n=read();m=read();s=read();t=read();
for(int i=1;i<=m;++i)
{
a=read();b=read();c=read();
add(a,b,c);
add(b,a,0);//添加反向边
}
cout<<isap()<<endl;
return 0;
}
以及加上弧优化后的版本:
就是加了个cur数组来标记弧
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const ll N=101000;
#define endl '\n'
const ll inf=1<<30;
struct ty{
ll t,l,next;
}edge[N<<1];
ll cnt=1;//后面要让第i条边^1后能得到它的反向边(与它相邻)
ll head[N];
inline ll 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;
}
void add(ll a,ll b,ll c)
{
edge[++cnt].t=b;
edge[cnt].l=c;
edge[cnt].next=head[a];
head[a]=cnt;
}
ll inque[N];
ll dep[N];//用于分层
ll gap[N];//该层深度的点有多少个
ll cur[N];//记录弧
ll vis;//这次是否能到达终点
ll ans=0;
ll n,m,s,t;
ll a,b,c;
void bfs()//倒着跑一遍,标记深度
{
memset(dep,-1,sizeof dep);
memset(gap,0,sizeof gap);
dep[t]=0;//从终点往起点推
gap[0]=1;
queue<ll> q;
q.push(t);
while(!q.empty())
{
ll ty=q.front();
q.pop();
for(int i=head[ty];i!=-1;i=edge[i].next)
{
ll y=edge[i].t;
if(dep[y]!=-1) continue;//代表它被访问过了
q.push(y);
dep[y]=dep[ty]+1;
gap[dep[y]]++;
}
}
}
ll dfs(ll u,ll flow)
{
if(u==t)
{
ans+=flow;
return flow;
}
ll used=0;
for(int i=cur[u];i!=-1;i=edge[i].next)
{
cur[u]=i;
ll y=edge[i].t;
if(edge[i].l&&dep[y]+1==dep[u])//这里顺序是反的//
{
ll fl=dfs(y,min(edge[i].l,flow-used));
if(fl)
{
edge[i].l-=fl;
edge[i^1].l+=fl;
used+=fl;
}
if(used==flow) return used;//前面送来的都用完了
}
}
//否则:
gap[dep[u]]--;
if(!gap[dep[u]]) dep[s]=n+1;
dep[u]++;
gap[dep[u]]++;
return used;
}
ll isap()
{
ans=0;
bfs();
while(dep[s]<n)
{
memcpy(cur,head,sizeof head);
dfs(s,inf);
}
return ans;
}
int main()
{
memset(head,-1,sizeof head);
n=read();m=read();s=read();t=read();
for(int i=1;i<=m;++i)
{
a=read();b=read();c=read();
add(a,b,c);
add(b,a,0);//添加反向边
}
cout<<isap()<<endl;
return 0;
}
至于HLPP。。。以后再说吧(可能都不一定会去学)
板子先总结到这,后面费用流也再说