网络流专题
前情提要
- 建议使用 B e l l m a n F o r d BellmanFord BellmanFord 算法跑最短路 . . .
最大流
D i n i c Dinic Dinic 算法
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
char InList[7500005];int inpos=7500000;
inline char GetChar()
{
if(inpos>=7500000)fread(InList,1,7500000,stdin),inpos=0;
return InList[inpos++];
}
template<typename Typename>
inline void Read(Typename &x)
{
x=0;
register char c=GetChar();
while(c<48||c>57)c=GetChar();
while(48<=c&&c<=57){x=x*10+c-48;c=GetChar();}
}
template<typename Typename>
inline void Read(Typename &x,Typename&y){
Read(x);Read(y);
}
template<typename Typename>
inline void Read(Typename &x,Typename&y,Typename&z){
Read(x,y);Read(z);
}
template<typename Typename>
inline void Read(Typename &a,Typename&b,Typename&c,Typename&d){
Read(a,b);Read(c,d);
}
template<typename Typename,int MaxCnt>
struct Queues{
private:
Typename Data[MaxCnt];
int head,tail,size;
public:
Queues(){
memset(Data,0,sizeof(Data));
head=tail=size=0;
}
inline void Clear(){
head=tail=size=0;
}
inline void Push(const Typename&x){
if(size==MaxCnt)puts("Error Queue");
else{
size++;
Data[tail]=x;
tail++;
if(tail==MaxCnt)tail=0;
}
}
inline void Pop(){
if(size==0)puts("Error Queue");
else{
size--;
head++;
if(head==MaxCnt)head=0;
}
}
inline Typename Front(){
if(size==0){puts("Error Queue");return Typename();}
else return Data[head];
}
inline bool Empty(){return(size==0);}
inline int Size(){return size;}
};
template<typename Typename,int MaxN,int MaxM>
struct Graphs{
int Last[MaxN],cntn;
Typename Flow[MaxM];
int To[MaxM],Next[MaxM],cntm;
Graphs(){
cntn=cntm=0;
memset(Last,-1,sizeof(Last));
}
inline void Clear(){
cntn=cntm=0;
memset(Last,-1,sizeof(Last));
}
};
template<typename Typename>
inline void Insert(Typename&G,int from,int to,long long flow1,long long flow2){
G.To[G.cntm]=to;G.Flow[G.cntm]=flow1;
G.Next[G.cntm]=G.Last[from];G.Last[from]=G.cntm++;
G.To[G.cntm]=from;G.Flow[G.cntm]=flow2;
G.Next[G.cntm]=G.Last[to];G.Last[to]=G.cntm++;
}
template<typename Typename>
inline Typename Min(const Typename&x,const Typename&y){
if(x<y)return x;return y;
}
template<typename Typename>
inline Typename Max(const Typename&x,const Typename&y){
if(x<y)return x;return y;
}
const long long Inf=0x3f3f3f3f3f3f3f3f;
const int MaxN=210;
const int MaxM=11000;
Graphs<long long,MaxN,MaxM>G;
Queues<int,MaxN>Q;
int st,ed,sizev;
bool Visit[MaxN];
long long High[MaxN];
inline bool Bfs(){
for(register int i=1;i<=sizev;i++)
High[i]=0;
High[ed]=1;Q.Clear();Q.Push(ed);
while(!Q.Empty()){
int from=Q.Front();Q.Pop();
for(register int k=G.Last[from];k!=-1;k=G.Next[k]){
if(G.Flow[k^1]>0&&High[G.To[k]]==0){
Q.Push(G.To[k]);
High[G.To[k]]=High[from]+1;
if(G.To[k]==st)return true;
}
}
}
return false;
}
int Cur[MaxN];
inline long long Dfs(int from,long long flow){
if(from==ed)return flow;
long long used=0,test;
for(register int k=Cur[from];k!=-1;k=G.Next[k]){
if(G.Flow[k]>0&&High[from]-1==High[G.To[k]]){
used+=(test=Dfs(G.To[k],Min(G.Flow[k],flow-used)));
G.Flow[k]-=test;G.Flow[k^1]+=test;
Cur[from]=k;
if(used==flow){return used;}
}
}
if(used==0)High[from]=0;
return used;
}
int n,m;
int main(){
Read(n,m,st,ed);
sizev=n;
for(register int i=1;i<=m;i++){
int x,y;long long f;
Read(x,y),Read(f);
Insert(G,x,y,f,0);
}
long long ans=0,test;
while(Bfs()){
memcpy(Cur+1,G.Last+1,sizeof(int)*n);
while(test=Dfs(st,Inf))ans+=test;
}
printf("%lld",ans);
return 0;
}
最小费用流
C a p a c i t y − S c a l i n g Capacity-Scaling Capacity−Scaling 算法
F r o m From From O u u a n Ouuan Ouuan
- D i j k s t r a Dijkstra Dijkstra 算法版
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
template<typename Typename>
inline Typename Max(const Typename&x,const Typename&y){
if(x>y)return x;return y;
}
template<typename Typename>
inline Typename Min(const Typename&x,const Typename&y){
if(x<y)return x;return y;
}
template<typename Typename>
inline void Swap(Typename&x,Typename&y){
Typename t=x;x=y;y=t;
}
template<typename Typename,int MaxCnt>
struct Heaps{
private:
Typename Data[MaxCnt];
int size;
inline void UpFix(int now){
int i=now>>1,j=now;//Dad[j]=i;
while(i!=0){
if(Data[i]<Data[j])break;
Swap(Data[i],Data[j]);
i>>=1;j>>=1;
}
}
inline void DownFix(int now){
int i=now,j=now<<1;
while(j<=size){
if(j+1<=size&&Data[j+1]<Data[j])j++;
if(Data[i]<Data[j])break;
Swap(Data[i],Data[j]);
i=j;j<<=1;
}
}
public:
Heaps(){
memset(Data,0,sizeof(Data));
size=0;
}
inline void Clear(){size=0;}
inline void Push(const Typename&x){
if(size==MaxCnt-1)puts("Error Heap");
else{
Data[++size]=x;
UpFix(size);
}
}
inline void Pop(){
Data[1]=Data[size--];
DownFix(1);
}
inline Typename Top(){
if(size==0){puts("Error Heap");return Typename();}
else return Data[1];
}
inline bool Empty(){return size==0;}
inline int Size(){return size;}
};
template<typename Typename,int MaxN,int MaxM>
struct Graphs{
int Last[MaxN],cntn;
Typename Cost[MaxM],Flow[MaxM];
int To[MaxM],Next[MaxM],cntm;
Graphs(){
cntn=cntm=0;
memset(Last,-1,sizeof(Last));
}
inline void Clear(){
cntn=cntm=0;
memset(Last,-1,sizeof(Last));
}
};
template<typename Typename>
inline void Insert(Typename&G,int from,int to,long long flow1,long long flow2,long long cost1,long long cost2){
G.To[G.cntm]=to;G.Flow[G.cntm]=flow1;G.Cost[G.cntm]=cost1;
G.Next[G.cntm]=G.Last[from];G.Last[from]=G.cntm++;
G.To[G.cntm]=from;G.Flow[G.cntm]=flow2;G.Cost[G.cntm]=cost2;
G.Next[G.cntm]=G.Last[to];G.Last[to]=G.cntm++;
}
template<typename Typename1,typename Typename2>
struct Pairs{
Typename1 x;Typename2 y;
Pairs(const Typename1 _x=Typename1(),Typename2 _y=Typename2()):
x(_x),y(_y){}
inline bool operator<(const Pairs<Typename1,Typename2>p)const{
return x==p.x?y<p.y:x<p.x;
}
};
const long long Inf=0x3f3f3f3f3f3f3f3f;
const long long Limit=1e12;
const int MaxN=6e3,MaxM=1e5+1e4;
Graphs<long long,MaxN,MaxM>G;
Heaps<Pairs<long long,int>,MaxM>H;
long long Weight[MaxN],Dist[MaxN],Flow[MaxM];
int Pre[MaxN];
inline long long CalcCost(const int&id){
return Weight[G.To[id^1]]+G.Cost[id]-Weight[G.To[id]];
}
int sizev;
bool Visit[MaxN];
void Dijkstra(int start)
{
for(register int i=1;i<=sizev+1;i++)
Visit[i]=false,Dist[i]=Inf,Pre[i]=-1;
Dist[start]=0;H.Push(Pairs<long long,int>(0ll,start));
while(!H.Empty()){
int from=H.Top().y;H.Pop();
if(Visit[from])continue;
Visit[from]=true;
for(int k=G.Last[from];k!=-1;k=G.Next[k]){
if(Flow[k]&&Dist[G.To[k]]>Dist[from]+CalcCost(k))
{
Dist[G.To[k]]=Dist[from]+CalcCost(k);
Pre[G.To[k]]=k;H.Push(Pairs<long long,int>(Dist[G.To[k]],G.To[k]));
}
}
}
}
int n;
inline void Augment(const int&id)
{
int from=G.To[id^1];
int to=G.To[id];
if(Flow[id]){
++Flow[id];
return;
}
Dijkstra(to);
if(Dist[from]<Inf&&Dist[from]+CalcCost(id)<0){
++Flow[id^1];
while(from!=to){
int x=Pre[from];
--Flow[x];
++Flow[x^1];
from=G.To[x^1];
}
}
else ++Flow[id];
long long maxi=0;
long long len=CalcCost(id);
for(register int i=1;i<=sizev;i++)
if(Dist[i]<Inf)maxi=Max(maxi,Dist[i]);
for(register int i=1;i<=sizev;i++)
Weight[i]+=Dist[i]<Inf?Dist[i]:maxi+Max(0ll,-len);
Dijkstra(n+1);
for(register int i=1;i<=sizev;i++)
Weight[i]+=Dist[i];
}
int m,s,t;
int main(){
scanf("%d%d%d%d",&n,&m,&s,&t);
sizev=n;
for(register int i=1;i<=m;i++){
int x,y;long long f,w;
scanf("%d%d%lld%lld",&x,&y,&f,&w);
if(x==y){if(w<0)mini+=f*w;}
else Insert(G,x,y,f,0,w,-w);
}
Insert(G,t,s,Limit,0,-Limit,Limit);
for(register int i=1;i<=n;i++){
Insert(G,n+1,i,0,0,0,0);
Flow[G.cntm-2]=1;
}
for(register int i=40;i>=0;i--){
for(register int j=0;j<=m*2+1;j++)Flow[j]<<=1;
for(register int j=0;j<=m*2;j+=2)
if((G.Flow[j]>>i)&1)
Augment(j);
}
long long mini=0;
for(register int i=0;i<m;i++)
mini+=Flow[i<<1|1]*G.Cost[i<<1];
printf("%lld %lld",Flow[m<<1|1],mini);
return 0;
}
- B e l l m a n F o r d BellmanFord BellmanFord 算法版
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
template<typename Typename>
inline Typename Max(const Typename&x,const Typename&y){
if(x>y)return x;return y;
}
template<typename Typename>
inline Typename Min(const Typename&x,const Typename&y){
if(x<y)return x;return y;
}
template<typename Typename>
inline void Swap(Typename&x,Typename&y){
Typename t=x;x=y;y=t;
}
template<typename Typename,int MaxCnt>
struct Queues{
private:
Typename Data[MaxCnt];
int head,tail,size;
public:
Queues(){
memset(Data,0,sizeof(Data));
head=tail=size=0;
}
inline void Clear(){
head=tail=size=0;
}
inline void Push(const Typename&x){
if(size==MaxCnt)puts("Error Queue");
else{
size++;
Data[tail]=x;
tail++;
if(tail==MaxCnt)tail=0;
}
}
inline void Pop(){
if(size==0)puts("Error Queue");
else{
size--;
head++;
if(head==MaxCnt)head=0;
}
}
inline Typename Front(){
if(size==0){puts("Error Queue");return Typename();}
else return Data[head];
}
inline bool Empty(){return(size==0);}
inline int Size(){return size;}
};
template<typename Typename,int MaxN,int MaxM>
struct Graphs{
int Last[MaxN],cntn;
Typename Cost[MaxM],Flow[MaxM];
int To[MaxM],Next[MaxM],cntm;
Graphs(){
cntn=cntm=0;
memset(Last,-1,sizeof(Last));
}
inline void Clear(){
cntn=cntm=0;
memset(Last,-1,sizeof(Last));
}
};
template<typename Typename>
inline void Insert(Typename&G,int from,int to,long long flow1,long long flow2,long long cost1,long long cost2){
G.To[G.cntm]=to;G.Flow[G.cntm]=flow1;G.Cost[G.cntm]=cost1;
G.Next[G.cntm]=G.Last[from];G.Last[from]=G.cntm++;
G.To[G.cntm]=from;G.Flow[G.cntm]=flow2;G.Cost[G.cntm]=cost2;
G.Next[G.cntm]=G.Last[to];G.Last[to]=G.cntm++;
}
const long long Inf=0x3f3f3f3f3f3f3f3f;
const long long Limit=1ll<<32;
const int MaxN=6e3,MaxM=1e5+1e4;
Graphs<long long,MaxN,MaxM>G;
Queues<int,MaxN>Q;
long long Dist[MaxN],Flow[MaxM];
int Pre[MaxN];
int sizev;
bool Visit[MaxN];
void BellmanFord(int start)
{
for(register int i=1;i<=sizev+1;i++)
Visit[i]=false,Dist[i]=Inf,Pre[i]=-1;
Dist[start]=0;Q.Push(start);
while(!Q.Empty()){
int from=Q.Front();Q.Pop();
for(int k=G.Last[from];k!=-1;k=G.Next[k]){
if(Flow[k]&&Dist[G.To[k]]>Dist[from]+G.Cost[k])
{
Dist[G.To[k]]=Dist[from]+G.Cost[k];
Pre[G.To[k]]=k;
if(!Visit[G.To[k]])Q.Push(G.To[k]);
}
}
Visit[from]=false;
}
}
int n;
inline void Augment(const int&id)
{
int from=G.To[id^1];
int to=G.To[id];
if(Flow[id]){
++Flow[id];
return;
}
BellmanFord(to);
if(Dist[from]<Inf&&Dist[from]+G.Cost[id]<0){
++Flow[id^1];
while(from!=to){
int x=Pre[from];
--Flow[x];
++Flow[x^1];
from=G.To[x^1];
}
}
else ++Flow[id];
}
int m,s,t;
int main(){
scanf("%d%d%d%d",&n,&m,&s,&t);
sizev=n;
long long mini=0;
for(register int i=1;i<=m;i++){
int x,y;long long f,w;
scanf("%d%d%lld%lld",&x,&y,&f,&w);
if(x==y){if(w<0)mini+=f*w;}
else Insert(G,x,y,f,0,w,-w);
}
Insert(G,t,s,Limit,0,-Limit,Limit);
for(register int i=32;i>=0;i--){
for(register int j=0;j<=m*2+1;j++)Flow[j]<<=1;
for(register int j=0;j<=m*2;j+=2)
if((G.Flow[j]>>i)&1)
Augment(j);
}
for(register int i=0;i<m;i++)
mini+=Flow[i<<1|1]*G.Cost[i<<1];
printf("%lld %lld",Flow[m<<1|1],mini);
return 0;
}
Z k w Zkw Zkw算法
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
template<typename Typename,int MaxCnt>
struct Queues{
private:
Typename Data[MaxCnt];
int head,tail,size;
public:
Queues(){
memset(Data,0,sizeof(Data));
head=tail=size=0;
}
inline void Clear(){
head=tail=size=0;
}
inline void Push(const Typename&x){
if(size==MaxCnt)puts("Error Queue");
else{
size++;
Data[tail]=x;
tail++;
if(tail==MaxCnt)tail=0;
}
}
inline void Pop(){
if(size==0)puts("Error Queue");
else{
size--;
head++;
if(head==MaxCnt)head=0;
}
}
inline Typename Front(){
if(size==0){puts("Error Queue");return Typename();}
else return Data[head];
}
inline bool Empty(){return(size==0);}
inline int Size(){return size;}
};
template<typename Typename,int MaxN,int MaxM>
struct Graphs{
int Last[MaxN],cntn;
Typename Cost[MaxM],Flow[MaxM];
int To[MaxM],Next[MaxM],cntm;
Graphs(){
cntn=cntm=0;
memset(Last,-1,sizeof(Last));
}
inline void Clear(){
cntn=cntm=0;
memset(Last,-1,sizeof(Last));
}
};
template<typename Typename>
inline void Insert(Typename&G,int from,int to,long long flow1,long long flow2,long long cost1,long long cost2){
G.To[G.cntm]=to;G.Flow[G.cntm]=flow1;G.Cost[G.cntm]=cost1;
G.Next[G.cntm]=G.Last[from];G.Last[from]=G.cntm++;
G.To[G.cntm]=from;G.Flow[G.cntm]=flow2;G.Cost[G.cntm]=cost2;
G.Next[G.cntm]=G.Last[to];G.Last[to]=G.cntm++;
}
template<typename Typename>
inline Typename Min(const Typename&x,const Typename&y){
if(x<y)return x;return y;
}
template<typename Typename>
inline Typename Max(const Typename&x,const Typename&y){
if(x<y)return x;return y;
}
const long long Inf=0x3f3f3f3f3f3f3f3f;
const int MaxN=1100;
const int MaxM=201000;
Graphs<long long,MaxN,MaxM>G;
Queues<int,MaxN>Q;
int st,ed,sizev;
bool Visit[MaxN];
long long Dist[MaxN];
inline bool BellmanFord(){
for(register int i=1;i<=sizev;i++)
Visit[i]=false,Dist[i]=Inf;
Q.Clear();Q.Push(ed);
Visit[ed]=true;Dist[ed]=0;
while(!Q.Empty()){
int from=Q.Front();Q.Pop();
for(register int k=G.Last[from];k!=-1;k=G.Next[k]){
if(G.Flow[k^1]>0&&G.Cost[k^1]+Dist[from]<Dist[G.To[k]]){
Dist[G.To[k]]=G.Cost[k^1]+Dist[from];
if(Visit[G.To[k]]==false){
Visit[G.To[k]]=true;
Q.Push(G.To[k]);
}
}
}
Visit[from]=false;
}
return Dist[st]!=Inf;
}
int Cur[MaxN];
long long cost;
inline long long Dfs(int from,long long flow){
if(from==ed)return flow;
Visit[from]=true;
long long used=0,test;
for(register int k=Cur[from];k!=-1;k=G.Next[k]){
if(!Visit[G.To[k]]&&G.Flow[k]>0&&Dist[from]-G.Cost[k]==Dist[G.To[k]]){
used+=(test=Dfs(G.To[k],Min(G.Flow[k],flow-used)));
G.Flow[k]-=test;G.Flow[k^1]+=test;cost+=test*G.Cost[k];
Cur[from]=k;
if(used==flow){Visit[from]=false;return used;}
}
}
Visit[from]=false;
return used;
}
int n,m;
int main(){
scanf("%d%d%",&n,&m);
st=1;ed=n;sizev=n;
for(register int i=1;i<=m;i++){
int x,y;long long f,c;
scanf("%d%d%lld%lld",&x,&y,&f,&c);
if(x==y){if(c<0)cost+=f*c;}//自环的处理
else Insert(G,x,y,f,0,c,-c);
}
long long ans=0;
while(BellmanFord()){
memcpy(Cur+1,G.Last+1,sizeof(int)*n);
while(test=Dfs(st,Inf))ans+=test;
}
printf("%lld %lld",ans,cost);
return 0;
}
上下界网络流
概述
上下界网络流本质是给流量网络的每一条边设置了流量上界 u ( u , v ) u(u,v) u(u,v) 和流量下界 l ( u , v ) l(u,v) l(u,v) . . . 也就是说 , , , 一种可行的流必须满足 l ( u , v ) ≤ f ( u , v ) ≤ u ( u , v ) l(u,v)\le f(u,v) \le u(u,v) l(u,v)≤f(u,v)≤u(u,v) . . . 同时必须满足除了源点和汇点之外的其余点流量平衡 . . .
根据题目要求 , , , 我们可以使用上下界网络流解决不同问题 . . .
无源汇上下界可行流
给定无源汇流量网络 G G G . . . 询问是否存在一种标定每条边流量的方式 , , , 使得每条边流量满足上下界同时每一个点流量平衡 . . .
不妨假设每条边已经流了 l ( u , v ) l(u,v) l(u,v) 的流量 , , , 设其为初始流 . . . 同时我们在新图中加入 u u u 连向 v v v 的流量为 u ( u , v ) − l ( u , v ) u(u,v)-l(u,v) u(u,v)−l(u,v) 的边 . . . 考虑在新图上进行调整 . . .
由于最大流需要满足初始流量平衡条件 ( ( ( 最大流可以看成是下界为 的上下界最大流 ) ) ) , , , 但是构造出来的初始流很有可能不满足初始流量平衡 . . . 假设一个点初始流入流量减初始流出流量为 M M M . . .
若 M = 0 M=0 M=0 , , , 此时流量平衡 , , , 不需要附加边 . . .
若 M > 0 M>0 M>0 , , , 此时入流量过大 , , , 需要新建附加源点 S ′ S' S′ , , , S ′ S' S′ 向其连流量为 M M M 的附加边 . . .
若 M < 0 M<0 M<0 , , , 此时出流量过大 , , , 需要新建附加汇点 T ′ T' T′ , , , 其向 T ′ T' T′ 连流量为 − M -M −M 的附加边 . . .
如果附加边满流 , , , 说明这一个点的流量平衡条件可以满足 , , , 否则这个点的流量平衡条件不满足 . . . ( ( ( 因为原图加上附加流之后才会满足原图中的流量平衡 ) ) )
在建图完毕之后跑 S ′ S' S′ 到 T ′ T' T′ 的最大流 , , , 若 S ′ S' S′ 连出去的边全部满流 , , , 则存在可行流 , , , 否则不存在 . . .
有源汇上下界可行流
给定有源汇流量网络 G G G . . . 询问是否存在一种标定每条边流量的方式 , , , 使得每条边流量满足上下界同时除了源点和汇点每一个点流量平衡 . . .
假设源点为 S S S , , , 汇点为 T T T . . .
则我们可以加入一条 T T T 到 S S S 的上界为 ∞ \infty ∞ , , , 下界为 0 0 0 的边转化为无源汇上下界可行流问题 . . .
若有解,则 S S S 到 T T T 的可行流流量等于 T T T 到 S S S 的附加边的流量 . . .
有源汇上下界最大流
给定有源汇流量网络 G G G . . . 询问是否存在一种标定每条边流量的方式 , , , 使得每条边流量满足上下界同时除了源点和汇点每一个点流量平衡 . . . 如果存在,询问满足标定的最大流量 . . .
我们找到网络上的任意一个可行流 . . . 如果找不到解就可以直接结束 . . .
否则我们考虑删去所有附加边之后的残量网络并且在网络上进行调整 . . .
我们在残量网络上再跑一次 S S S 到 T T T 的最大流 , , , 将可行流流量和最大流流量相加即为答案 . . .
S S S 到 T T T 的最大流直接在跑完有源汇上下界可行的残量网络上跑 . . .
千万不可以在原来的流量网络上跑 . . .
有源汇上下界最小流
给定有源汇流量网络 G G G . . . 询问是否存在一种标定每条边流量的方式 , , , 使得每条边流量满足上下界同时除了源点和汇点每一个点流量平衡 . . . 如果存在 , , ,询问满足标定的最小流量 . . .
类似的 , , , 我们考虑将残量网络中不需要的流退掉 . . .
我们找到网络上的任意一个可行流 , , , 如果找不到解就可以直接结束 . . .
否则我们考虑删去所有附加边之后的残量网络 . . .
我们在残量网络上再跑一次 T T T 到 S S S 的最大流 , , , 将可行流流量减去最大流流量即为答案 . . .
F r o m From From OI-wiki
构图技巧
点边互化
有向图中
,
,
, 若点
x
x
x 作为起点
,
,
, 则连有向边
(
x
′
,
y
)
(x',y)
(x′,y)
,
,
, 否则连有向边
(
y
,
x
)
(y,x)
(y,x)
.
.
.
最后使所有点
x
x
x
,
,
, 连边
(
x
,
x
′
)
(x,x')
(x,x′)
.
.
.
无向图中
,
,
, 若有边
(
x
,
y
)
(x,y)
(x,y)
,
,
, 则连有向边
(
x
′
,
y
)
(x',y)
(x′,y) 和有向边边
(
y
′
,
x
)
(y',x)
(y′,x)
.
.
.
最后使所有点
x
x
x
,
,
, 连边
(
x
,
x
′
)
(x,x')
(x,x′)
.
.
.
任何图中 , , , 边 ( x , y ) (x,y) (x,y) 可以变为 ( x , e ) (x,e) (x,e) 和 ( e , y ) (e,y) (e,y) , , , e e e 是新建的节点 , , , 注意新边的方向 . . .
最好自己用手模拟一遍 . . .