最大流

定义:有m条管道,n个节点,1为水源(源点),n为终点(汇点),每条管道有水流量上限,问如何分配每条水管的流量才能使终点处接受到的水流量最大。

流:从源点开始,在汇点结束的路径,有大小(即流量)。
容量网络:初始输入的各边的容量。
流量网络:计算中已有的水流量,即对汇点做出贡献的流量。
残量网络:容量网络-流量网络(开始就是容量网络)。
增广路:在残量网络上建立的新的合法的可以对汇点做出贡献的一条流。

SRE实战 互联网时代守护先锋,助力企业售后服务体系运筹帷幄!一键直达领取阿里云限量特价优惠。

最小割最大流定理:是指在一个网络流中,能够从源点到达汇点的最大流量等于如果从网络中移除就能够导致网络流中断的边的集合的最小容量和。

EK(最短单路增广路)

寻找最短的新的增广路,累积贡献,直至找不到为止。
实现:每次通过bfs寻找增广路,每次加入一条新的边,同时建立一条反向边(可以在一条流建立之后,将这条流某部分路径换道,使其他流能占用这个路径中的边。)

显然在很多情况这种单路增广会非常的慢(极限复杂度\(O(n*m^2)\))

#include <bits/stdc++.h>
using namespace std;
const int maxn=1e4+5;
const int maxm=1e6+5;
const int INF=1e9;
struct edge{
    int v,w,next;
}E[maxm];
int tot=1,head[maxn];
void addedge(int u,int v,int w){
    E[++tot].v=v;
    E[tot].w=w;
    E[tot].next=head[u];
    head[u]=tot;
}
int n,m,s,t;
int flow[maxn],q[maxn],fa[maxn],pos[maxn];
bool bfs(){
    fill(flow,flow+n+1,0);
    int l=0,r=1;
    q[r]=s;
    flow[s]=INF;
    while(l!=r){
        int u=q[l=l==n?0:l+1];
        for(int i=head[u];i;i=E[i].next){
            int &v=E[i].v,&w=E[i].w;
            if(w&&!flow[v]){
                fa[v]=u;
                pos[v]=i;
                q[r=r==n?0:r+1]=v;
                flow[v]=min(w,flow[u]);
            }
            if(flow[t])break;
        }
        if(flow[t])break;
    }
    return flow[t]>0;
}
int maxf;
void EK(){
    maxf=0;
    while(bfs()){
        for(int u=t;u!=s;u=fa[u]){
            int e=pos[u];
            E[e].w-=flow[t];
            E[e^1].w+=flow[t];
        }
        maxf+=flow[t];
    }
}
int main(){
    cin>>n>>m>>s>>t;
    for(int i=1;i<=m;i++){
        int u,v,w;
        scanf("%d%d%d",&u,&v,&w);
        addedge(u,v,w);
        addedge(v,u,0);
    }
    EK();
    printf("%d\n",maxf);
}

Dinic(分层多路增广)

在EK寻找增广路地基础上,每次多路增广:u 点通过一条边,向 v 输出流量以后,v 会尝试到达汇点(到达汇点才真正增广),然后 v 返回实际增广量。这时,如果 u 还有没用完的供给,就继续尝试输出到其它边。

但是这样就可能会出现绕远路、甚至绕回路的情况,如何避免这种情况?

源点顺着残量网络想要到达其它点,需要经过一些边,按照经过的边数(即源点出发以后的距离)把图分层,即用 bfs 分层。每次尝试给予时,只考虑给予自己下一层的点,就可以防止混乱。

实现方法:每次bfs先按照当前残量网络分一次层,随后dfs多路增广,尽可能增加流量。一直到 bfs 分层时搜不到汇点(即残量网络断了)为止。(极限复杂度\(O(n^2*m)\))

当前弧优化:dfs的时候一个点会被多个点遍历到,如果这个点的一条边已经被遍历过,那么之后就不用再遍历了(因为已经尽可能地从这条边增广了),可以用数组记录每个点遍历邻边时的起始边(是单调增的的)。

//注意初始化时tot一定要置为1
#include <bits/stdc++.h>
using namespace std;
const int maxm=2e5+5;
const int maxn=1e4+5;
struct edge{
    int to,next,w;
}E[maxm];
int tot=1,head[maxn],cur[maxn];//tot=1便于取反向边
void addedge(int u,int v,int w){
    E[++tot].to=v;
    E[tot].w=w;
    E[tot].next=head[u];
    head[u]=tot;
}
void add(int u,int v,int w){
    addedge(u,v,w);
    addedge(v,u,0);
}
int n,m,start,target;
int dep[maxn],q[maxn];
bool bfs(){
    fill(dep,dep+1+n,0);
    int l=0,r=1;
    q[r]=start;
    dep[start]=1;
    while(l!=r){
        int u=q[l=l==n?0:l+1];
        for(int i=head[u];i;i=E[i].next){
            int v=E[i].to;
            if(E[i].w&&!dep[v]){
                dep[v]=dep[u]+1;
                q[r=r==n?0:r+1]=v;
            }
        }
    }
    return dep[target]>0;
}
int dfs(int u,int in){
    if(u==target)
        return in;
    int out=0;
    for(int i=cur[u];i&&in;i=E[i].next){
        cur[u]=i;//当前弧优化
        int v=E[i].to;
        if(E[i].w&&dep[v]==dep[u]+1){
            int res=dfs(v,min(E[i].w,in));
            E[i].w-=res;
            E[i^1].w+=res;
            in-=res;
            out+=res;
        }
    }
    if(out==0)dep[u]=-1;//该点无法到达终点,删去
    return out;
}
int dinic(){
    int maxf=0;
    while(bfs()){
        for(int i=1;i<=n;i++)cur[i]=head[i];//当前弧初始化
        maxf+=dfs(start,2e9);
    }
    return maxf;
}
void init(){
    memset(head,0,sizeof(head));
    tot=1;//便于取反向边
}
int main(){
    init();
    cin>>n>>m>>start>>target;
    for(int i=1;i<=m;i++){
        int u,v,w;
        scanf("%d%d%d",&u,&v,&w);
        add(u,v,w);
    }
    int ans=dinic();
    printf("%d\n",ans);
}

最小费用最大流

EK单路增广费用流

可以看成EK算法的变形,将EK的bfs标号改为用最短路算法,每次找出费用最小的能到达终点的流,直至没有增广路为止。(显然在很多情况这种单路增广会非常的慢)

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxm=2e5+5;
const int maxn=1e4+5;
const int INF=2e9;
struct edge {
    int v,next,w,f;
} E[maxm];
int tot=1,head[maxn];//tot必须设为1才能使反向边用^得到
void addedge(int u,int v,int w,int f) {
    E[++tot].v=v;
    E[tot].w=w;
    E[tot].f=f;
    E[tot].next=head[u];
    head[u]=tot;
}
int n,m,s,t;
int dis[maxn],vis[maxn],pos[maxn],fa[maxn],flow[maxn];
int q[maxn];
bool spfa(){
    fill(dis,dis+n+1,INF);
    fill(vis,vis+n+1,0);
    int l=0,r=1;
    q[1]=s;
    dis[s]=0;
    flow[s]=INF;
    while(l!=r){
        int u=q[l=l==n?0:l+1];
        vis[u]=0;
        for(int i=head[u];i;i=E[i].next){
            int v=E[i].v,w=E[i].w,f=E[i].f;
            if(w&&dis[u]+f<dis[v]){//可以流,并且可以松弛
                flow[v]=min(w,flow[u]);
                pos[v]=i;//记录前向边
                fa[v]=u;//记录最短路径上的点
                dis[v]=dis[u]+f;
                if(!vis[v]){
                    if(dis[v]>dis[l+1])q[r=r==n?0:r+1]=v;
                    else q[l]=v,l=l==0?n:l-1;//SLF优化
                    vis[v]=1;
                }
            }
        }
    }
    return dis[t]!=INF;
}
int maxf,cost;
void mincmaxf() {
    maxf=0;
    cost=0;
    while(spfa()){
        maxf+=flow[t];
        cost+=flow[t]*dis[t];
        for(int u=t; u!=s; u=fa[u]){
            int e=pos[u];
            E[e].w-=flow[t];
            E[e^1].w+=flow[t];
        }
    }
}
int main() {
    cin>>n>>m>>s>>t;
    for(int i=1;i<=m; i++) {
        int u,v,w,f;
        scanf("%d%d%d%d",&u,&v,&w,&f);
        addedge(u,v,w,f);
        addedge(v,u,0,-f);
    }
    mincmaxf();
    printf("%d %d\n",maxf,cost);
}

ZKW多路增广费用流

最短路不一定只有一条,如何跑一次SPFA后能使所有最短路都能尽可能得增广?

可以看成dinic算法的变形,只要倒着跑(从终点出发)SPFA,然后从起点u开始dfs,遍历u相邻的点v,边权为f,如果dis[u]=dis[v]+f,则v在到终点的最短路上,继续dfs(v)(在每个点都将流量尽量耗尽,使得一次多路增广后图上已经不能在最短路上找到增广路)

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxm=2e5+5;
const int maxn=1e4+5;
const int INF=1e9;
struct edge {
    int v,next,w,f;
} E[maxm];
int tot=1,head[maxn];//tot必须设为1才能使反向边用^得到
void addedge(int u,int v,int w,int f) {
    E[++tot].v=v;
    E[tot].w=w;
    E[tot].f=f;
    E[tot].next=head[u];
    head[u]=tot;
}
void add(int u,int v,int w,int f){
    addedge(u,v,w,f);
    addedge(v,u,0,-f);
}
int n,m,start,target;
int dis[maxn],vis[maxn];
int q[maxn];
bool spfa(){
    fill(dis,dis+n+1,INF);
    fill(vis,vis+n+1,0);
    int l=0,r=1;
    q[1]=target;
    dis[target]=0;//反向跑最短路
    while(l!=r){
        int u=q[l=l==n?0:l+1];
        vis[u]=0;
        for(int i=head[u];i;i=E[i].next){
            int v=E[i].v;
            int f=-E[i].f;//当前遍历的是反向的边,费用是负的
            if(E[i^1].w&&dis[v]>dis[u]+f){//反向的边才是流的时候走的边,容量要>0
                dis[v]=dis[u]+f;
                if(!vis[v]){
                    if(dis[v]>dis[l+1])q[r=r==n?0:r+1]=v;
                    else q[l]=v,l=l==0?n:l-1;//SLF优化
                    vis[v]=1;
                }
            }
        }
    }
    return dis[start]<INF;
}
int maxf,cost;
int dfs(int u,int flow){
    if(u==target){
        vis[target]=1;
        return flow;
    }
    int used=0;
    vis[u]=1;
    for(int i=head[u];i;i=E[i].next){
        int v=E[i].v,w=E[i].w,f=E[i].f;
        if(!vis[v]&&w&&dis[u]==dis[v]+f){//满足增广条件
            int temp=dfs(v,min(w,flow-used));
            if(temp){
                cost+=temp*f;
                E[i].w-=temp;
                E[i^1].w+=temp;
                used+=temp;
            }
            if(used==flow)break;
        }
    }
    return used;
}
void mincmaxf() {
    maxf=cost=0;
    while(spfa()) {
        vis[target]=1;
        while(vis[target]) {
            memset(vis,0,sizeof vis);
            maxf+=dfs(start,1e9);//一直増广直到走不到为止
        }
    }
}
int main() {
    cin>>n>>m>>start>>target;
    for(int i=1;i<=m; i++) {
        int u,v,w,f;
        scanf("%d%d%d%d",&u,&v,&w,&f);
        add(u,v,w,f);
    }
    mincmaxf();
    printf("%d %d\n",maxf,cost);
}

例题

Path(最小割)(最短路+最大流)

题目链接

题意:给定一个边权有向图,问用最小的代价(边权和最小)切断一些边,使得起点到终点的最短路变长。

思路:
将所有最短路合并成新的图,该图的最小割(最大流)就是解。
最短路建图的方法:枚举每条边,若该边到起点与终点的距离和+该边边权=最短路,该边就在最短路上。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll maxm=2e5+5;
const ll maxn=1e4+5;
const ll INF=1e17;
struct edge{
    ll to,next,w;
}E[maxm];
ll tot=1,head[maxn],cur[maxn];//tot=1便于取反向边
void addedge(ll u,ll v,ll w){
    E[++tot].to=v;
    E[tot].w=w;
    E[tot].next=head[u];
    head[u]=tot;
}
void add(int u,int v,int w){
    addedge(u,v,w);
    addedge(v,u,0);
}
ll n,m,start,target;
ll dep[maxn],q[maxn];
bool bfs(){
    fill(dep,dep+1+n,0);
    ll l=0,r=1;
    q[r]=start;
    dep[start]=1;
    while(l!=r){
        ll u=q[l=l==n?0:l+1];
        for(ll i=head[u];i;i=E[i].next){
            ll v=E[i].to;
            if(E[i].w&&!dep[v]){
                dep[v]=dep[u]+1;
                q[r=r==n?0:r+1]=v;
            }
        }
    }
    return dep[target]>0;
}
ll dfs(ll u,ll in){
    if(u==target)
        return in;
    ll out=0;
    for(ll i=cur[u];i&&in;i=E[i].next){
        cur[u]=i;//当前弧优化
        ll v=E[i].to;
        if(E[i].w&&dep[v]==dep[u]+1){
            ll res=dfs(v,min(E[i].w,in));
            E[i].w-=res;
            E[i^1].w+=res;
            in-=res;
            out+=res;
        }
    }
    if(out==0)dep[u]=-1;//该点无法到达终点,删去
    return out;
}
ll dinic(){
    ll maxf=0;
    while(bfs()){
        for(ll i=1;i<=n;i++)cur[i]=head[i];//当前弧初始化
        maxf+=dfs(start,2e9);
    }
    return maxf;
}
struct Node{
    ll id,cost;
    Node(ll a=0,ll b=0):id(a),cost(b){}
    bool operator<(const Node &b)const{
        return cost>b.cost;//将小的放上面
    }
};
struct Edge{
    ll v,cost;
    Edge(ll a=0,ll b=0):v(a),cost(b){}
};
vector<Edge>Eg[maxm];
ll dis1[maxn],dis2[maxn];
bool vis[maxn];
void dijkstra(ll n,ll start,ll* lowcost){
    fill(vis,vis+1+n,0);
    fill(lowcost,lowcost+1+n,INF);
    priority_queue<Node>q;
    lowcost[start]=0;
    q.push(Node(start,0));
    while(!q.empty() ){
        ll u=q.top().id;
        q.pop();
        if(vis[u])continue;
        vis[u]=1;
        for(ll i=0;i<Eg[u].size();i++){
            ll v=Eg[u][i].v;
            ll cost=Eg[u][i].cost;
            if(!vis[v]&&lowcost[v]>lowcost[u]+cost){
                lowcost[v]=lowcost[u]+cost;
                q.push(Node(v,lowcost[v]) );
            }
        }
    }
}
void Addedge(ll u,ll v,ll w){
    Eg[u].push_back(Edge(v,w));
}
void init1(ll nn){
    for(ll i=1;i<=nn;i++){
        Eg[i].clear();
    }
}
void init2(){
    memset(head,0,sizeof(head));
    tot=1;
}
ll U[maxn],V[maxn],W[maxn];
int main(){
    ll T,nn,mm;
    cin>>T;
    while(T--){
        scanf("%lld%lld",&nn,&mm);
        init1(nn);
        for(ll i=1;i<=mm;i++){
            scanf("%lld%lld%lld",&U[i],&V[i],&W[i]);
            Addedge(U[i],V[i],W[i]);
        }
        dijkstra(nn,1,dis1);
        if(dis1[nn]==INF){
            printf("0\n");
            continue;
        }
        init1(nn);
        for(ll i=1;i<=mm;i++){
            Addedge(V[i],U[i],W[i]);
        }
        dijkstra(nn,nn,dis2);
        init2();
        for(ll i=1;i<=mm;i++){
            if(dis1[U[i]]+dis2[V[i]]+W[i]==dis1[nn]){
                add(U[i],V[i],W[i]);
            }
        }
        start=1,target=nn;
        n=nn;
        ll ans=dinic();
        printf("%lld\n",ans);
    }
}

Magic Potion(最大流)

题目链接

题意:

有n个人,m个怪,k瓶药,第i个人能杀怪物的编号为集合a[i],每个人只能杀一只怪 ,嗑药之后能多杀一只,但是一个人最多只能嗑一瓶药,问所有人最多能杀多少怪。

思路:

转化为最大流问题,首先建立一个源点,源点到每个人有一条权值为1的边(每人只能杀一只怪),每个人到他能杀的怪有一条权值为1的边,每个怪到汇点有一条权值为1的边。考虑到嗑药问题,建立转折点ch,汇点到ch有一条权值为k的边,ch到所有人有一条权值为1的边,在源点到汇点的图上跑一次最大流即可得出答案。

//注意初始化时tot一定要置为1
#include <bits/stdc++.h>
using namespace std;
const int maxm=2e5+5;
const int maxn=1e4+5;
struct edge{
    int to,next,w;
}E[maxm];
int tot=1,head[maxn],cur[maxn];//tot=1便于取反向边
void addedge(int u,int v,int w){
    E[++tot].to=v;
    E[tot].w=w;
    E[tot].next=head[u];
    head[u]=tot;
}
void add(int u,int v,int w){
    addedge(u,v,w);
    addedge(v,u,0);
}
int n,m,start,target;
int dep[maxn],q[maxn];
bool bfs(){
    fill(dep,dep+1+n,0);
    int l=0,r=1;
    q[r]=start;
    dep[start]=1;
    while(l!=r){
        int u=q[l=l==n?0:l+1];
        for(int i=head[u];i;i=E[i].next){
            int v=E[i].to;
            if(E[i].w&&!dep[v]){
                dep[v]=dep[u]+1;
                q[r=r==n?0:r+1]=v;
            }
        }
    }
    return dep[target]>0;
}
int dfs(int u,int in){
    if(u==target)
        return in;
    int out=0;
    for(int i=cur[u];i&&in;i=E[i].next){
        cur[u]=i;//当前弧优化
        int v=E[i].to;
        if(E[i].w&&dep[v]==dep[u]+1){
            int res=dfs(v,min(E[i].w,in));
            E[i].w-=res;
            E[i^1].w+=res;
            in-=res;
            out+=res;
        }
    }
    if(out==0)dep[u]=-1;//该点无法到达终点,删去
    return out;
}
int dinic(){
    int maxf=0;
    while(bfs()){
        for(int i=1;i<=n;i++)cur[i]=head[i];//当前弧初始化
        maxf+=dfs(start,2e9);
    }
    return maxf;
}
void init(){
    memset(head,0,sizeof(head));
    tot=1;//便于取反向边
}
int main(){
    init();
    int N,M,K;
    cin>>N>>M>>K;
    start=N+M+1;
    target=N+M+2;
    int ch=N+M+3;
    n=N+M+3;
    for(int i=1;i<=N;i++){
        int siz;
        scanf("%d",&siz);
        while(siz--){
            int v;
            scanf("%d",&v);
            add(i,N+v,1);
        }
    }
    add(start,ch,K);
    for(int i=1;i<=N;i++){
        add(start,i,1);
        add(ch,i,1);
    }
    for(int i=1;i<=M;i++){
        add(N+i,target,1);
    }
    int ans=dinic();
    printf("%d\n",ans);
}

运输问题(最小费用/最大费用 最大流)

题目链接

题意:

公司有 m个仓库和 n个零售商店。第 i个仓库有 a个单位的货物;第 j个零售商店需要 b个单位的货物。

货物供需平衡,即\(\sum\limits_{i=1}^{m}a_i=\sum\limits_{j=1}^{n}b_j\)

从第i个仓库运送每单位货物到第 j 个零售商店的费用为Cij 。

问使所有货物都运走,总费用最大为多少,最小为多少?

思路:

求最小是最小费用最大流模板,求最大只要把边全部变成相反数的再跑一遍即可。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxm=1e5+5;
const int maxn=205;
const int INF=1e9;
struct edge {
    int v,next,w,f;
} E[maxm];
int tot=1,head[maxn];//tot必须设为1才能使反向边用^得到
void addedge(int u,int v,int w,int f) {
    E[++tot].v=v;
    E[tot].w=w;
    E[tot].f=f;
    E[tot].next=head[u];
    head[u]=tot;
}
int n,m,s,t;
int dis[maxn],vis[maxn];
int q[maxn];
bool spfa(){
    fill(dis,dis+n+1,INF);
    fill(vis,vis+n+1,0);
    int l=0,r=1;
    q[1]=t;
    dis[t]=0;//反向跑最短路
    while(l!=r){
        int u=q[l=l==n?0:l+1];
        vis[u]=0;
        for(int i=head[u];i;i=E[i].next){
            int v=E[i].v;
            int f=-E[i].f;//当前遍历的是反向的边,费用是负的
            if(E[i^1].w&&dis[v]>dis[u]+f){//反向的边才是流的时候走的边,容量要>0
                dis[v]=dis[u]+f;
                if(!vis[v]){
                    if(dis[v]>dis[l+1])q[r=r==n?0:r+1]=v;
                    else q[l]=v,l=l==0?n:l-1;//SLF优化
                    vis[v]=1;
                }
            }
        }
    }
    return dis[s]<INF;
}
int maxf,cost;
int dfs(int u,int flow){
    if(u==t){
        vis[t]=1;
        return flow;
    }
    int used=0;
    vis[u]=1;
    for(int i=head[u];i;i=E[i].next){
        int v=E[i].v,w=E[i].w,f=E[i].f;
        if(!vis[v]&&w&&dis[u]==dis[v]+f){//满足增广条件
            int temp=dfs(v,min(w,flow-used));
            if(temp){
                cost+=temp*f;
                E[i].w-=temp;
                E[i^1].w+=temp;
                used+=temp;
            }
            if(used==flow)break;
        }
    }
    return used;
}
void mincmaxf() {
    maxf=cost=0;
    while(spfa()) {
        vis[t]=1;
        while(vis[t]) {
            memset(vis,0,sizeof vis);
            maxf+=dfs(s,1e9);//一直増广直到走不到为止
        }
    }
}
void add(int u,int v,int w,int f){
    addedge(u,v,w,f);
    addedge(v,u,0,-f);
}
int a[105],b[105],c[105][105];
int main() {
    int nn,mm;
    cin>>mm>>nn;
    n=nn+mm+2;
    s=nn+mm+1;
    t=nn+mm+2;
    for(int i=1;i<=mm;i++){
        scanf("%d",&a[i]);
        add(s,i,a[i],0);
    }
    for(int i=1;i<=nn;i++){
        scanf("%d",&b[i]);
        add(mm+i,t,b[i],0);
    }
    for(int i=1;i<=mm;i++){
        for(int j=1;j<=nn;j++){
            scanf("%d",&c[i][j]);
            add(i,mm+j,1e9,c[i][j]);
        }
    }
    mincmaxf();
    printf("%d\n",cost);
    //重新建立费用为相反数的模型
    tot=1;
    memset(head,0,sizeof(head));
    for(int i=1;i<=mm;i++){
        add(s,i,a[i],0);
    }
    for(int i=1;i<=nn;i++){
        add(mm+i,t,b[i],0);
    }
    for(int i=1;i<=mm;i++){
        for(int j=1;j<=nn;j++){
            add(i,mm+j,1e9,-c[i][j]);
        }
    }
    mincmaxf();
    printf("%d\n",-cost);
}

试题库问题(二分图特殊匹配)

题目链接

题意:

有n道题,k种题目类型。从n道题选出m道组成一套试卷。给出k个要求,即每种类型的题目要达到ki个(\(\sum ki=m\))。一道题可以代表多种类型,但是在试卷中一题只能选择一种类型。问是否有满足要求的方案,若有将其输出(每种类型的题有哪几道)

思路:

显然是一个最大流模型,源点到每道题有一条容量为1的边,每道题到对应类型有一条容量为1的边,每个类型i到汇点有一条容量为ki的边。若最大流不等于\(\sum ki\),则没有符合要求的方案,若等于则有。

遍历n道题的边(正向边),若u的一条边容量为0,指向v,则说明uv被匹配,将u加入v的答案中即可。

#include <bits/stdc++.h>
using namespace std;
const int maxm=2e5+5;
const int maxn=1e4+5;
struct edge{
    int to,next,w;
}E[maxm];
int tot=1,head[maxn],cur[maxn];
void addedge(int u,int v,int w){
    E[++tot].to=v;
    E[tot].w=w;
    E[tot].next=head[u];
    head[u]=tot;
}
int n,m,s,t;
int dep[maxn],q[maxn];
bool bfs(){
    fill(dep,dep+1+n,0);
    int l=0,r=1;
    q[r]=s;
    dep[s]=1;
    while(l!=r){
        int u=q[l=l==n?0:l+1];
        for(int i=head[u];i;i=E[i].next){
            int v=E[i].to;
            if(E[i].w&&!dep[v]){
                dep[v]=dep[u]+1;
                q[r=r==n?0:r+1]=v;
            }
        }
    }
    return dep[t]>0;
}
int dfs(int u,int in){
    if(u==t)
        return in;
    int out=0;
    for(int i=cur[u];i&&in;i=E[i].next){
        cur[u]=i;//当前弧优化
        int v=E[i].to;
        if(E[i].w&&dep[v]==dep[u]+1){
            int res=dfs(v,min(E[i].w,in));
            E[i].w-=res;
            E[i^1].w+=res;
            in-=res;
            out+=res;
        }
    }
    if(out==0)dep[u]=-1;//该点无法到达终点,删去
    return out;
}
int maxf;
void dinic(){
    maxf=0;
    while(bfs()){
        for(int i=1;i<=n;i++)cur[i]=head[i];//当前弧初始化
        maxf+=dfs(s,2e9);
    }
}
void add(int u,int v,int w){
    addedge(u,v,w);
    addedge(v,u,0);
}
int ki[maxn];
int main(){
    int nn,kk;
    cin>>kk>>nn;
    s=nn+kk+1;
    t=nn+kk+2;
    n=nn+kk+2;
    int sum=0;
    for(int i=1;i<=kk;i++){
        scanf("%d",&ki[i]);
        sum+=ki[i];
        add(nn+i,t,ki[i]);
    }
    for(int i=1;i<=nn;i++){
        int p;
        scanf("%d",&p);
        add(s,i,1);
        while(p--){
            int typ;
            scanf("%d",&typ);
            add(i,nn+typ,1);
        }
    }
    dinic();
    if(maxf!=sum){
        printf("No Solution!\n");
        return 0;
    }
    set<int>ans[25];
    for(int i=1;i<=nn;i++){
        for(int j=head[i];j;j=E[j].next){
            int w=E[j].w,v=E[j].to;
            if(!w){
                ans[v-nn].insert(i);
                break;
            }
        }
    }
    for(int i=1;i<=kk;i++){
        printf("%d:",i);
        for(auto x:ans[i]){
            printf(" %d",x);
        }
        puts("");
    }
}

参考资料:

https://www.luogu.org/blog/cicos/Maxflow-S-to-D

https://blog.csdn.net/jzq233jzq/article/details/73123089

扫码关注我们
微信号:SRE实战
拒绝背锅 运筹帷幄