网络流应用专题及最全套路详解,掌握这些你的网络流就没问题啦(上)

前言

不断更新中……
这几天新坑填不下去了,回来回顾一些经典的模型套路,先拿网络流开刀,窃以为洛谷这几道网络流的题目还是非常具有代表性的,涵盖了网络流调整多解计数最小割最大权闭合子图问题。
还涵盖了图论(二分图)中的一些结论和:最小不相交路径覆盖、最小可相交路径覆盖、二分图最大点权独立集、二分图最小点权覆盖集等问题,这里将简略介绍一下。

本专题包含六道题:P2765、P2764、P2763、P2766、P2774、P2805

P2765 网络流调整技巧
P2763 二分图多重匹配问题
P2764 最小不相交路径覆盖、最小可相交路径覆盖
P2766 一类dp计数问题、最多不相交路径
P2774 二分图最大点权独立集、二分图最小点权覆盖集
P2805 最大权闭合子图

前提

  • 知道网络流的基本用法。
  • 知道最大流最小割定理。
  • 知道二分图的定义。
  • 有一个稳定好用的Dinic求网络流的板子。

P2765 魔术球问题

前言

这道题应该可以通过贪心的方法过,不过既然放在了网络流专题里面,我们就认真的用网络流试一试并总结一下经验。

题解

添加一个小球相当于在网络中出现了一个从源点 S 到汇点 T 的流量。

分析一下题目:同一个柱子的小球要求可以与前一个小球形成和为完全平方数的情况,当一个新的小球到来的时候,可以加入其中的一根柱子,当然也可以另外开启一个新的柱子,使用的柱子的总数不得超过 n

我们另开一个节点 T 用来表示柱子的上限,连接一条容量为 n 的边 ( T , T )

我们尝试把小球拆成两个点 B B ,并且由 S B 连接一条容量为 1 的边。由 B T 连接一条容量为 1 的边,为什么容量为 1 ?这限制了每个球后面都只能紧邻地放一个球。

如果数大的小球与某个数小的小球之间组合满足和为完全平方数(即大球可以放在小球后面),我们就从连接一条容量为 1 的边 ( B , B ) 。这代表该大球可以放在小球后面不需要开启新的柱子。

每个球都可以连接一条容量为 1 的边 ( B , T ) 。代表该小球独立开启了一个新的柱子。

如下图所示:
这里写图片描述
回顾一下这张图:
B 流向 T 的流量代表 B 球开启了一个新的柱子,而从 B B 的流量代表 B 放在了 B 的后面。

这只是建图模型,我们在解决这道题的时候不能跑一遍网络流完事,而是需要使用迭代加深搜索的思想,增量式建图,每次加入一个球,然后在残余网络中跑最大流,判断能否跑满,如果跑不满,那么答案就是上次跑满时候的结果。

最后,这道题要求输出方案,我们只需要在残余网络里面遍历去边的容量,然后就可以得到方案了,这里不必细说,看我的代码就可以了。

参考代码 见附录

放置、匹配的问题,都可以往网络流方面思考。


P2764 最小路径覆盖

什么是最小路径覆盖?

最小路径覆盖分为两种

  • 最小不相交路径覆盖
  • 最小可相交路径覆盖

最小不相交路径覆盖的做法

构造一个新图,将一个点 B 拆成 B 1 B 2 两个点,在原图中如果有边 ( A , B ) 那么在新图中连接边 ( A 1 , B 2 )
然后形成了一张二分图,结论就是最小不相交路径覆盖=原图节点数-二分图的最大匹配

如何理解这个过程呢?

一开始每个点都是一条路径。
在二分图中,每形成一个匹配,就相当于一条路径将匹配两边的点给合并了,也就是合并了两个路径,这样一直匹配下去,路径数就在不断的减少,所以最终形成的就是最小路径覆盖,最后的路径数就是一开始原图的节点数-匹配数。

最小可相交路径覆盖的做法

这个问题与本题没有关系,只是扩展,可以暂时跳过。
解这个小问的方法是把这个问题化归到最小不相交路径覆盖上去。

可相交也就是说如果两点可达,那么不管这两点中间有多少节点,我都可以去经过。

先跑出可达矩阵,用Floyd算法,算出两两点之间的可达性。
然后如果两两可达,则建边。建成新图以后,就变成了最小不相交路径覆盖问题了。

理解

但是如果两个点a和b是连通的,只不过中间需要经过其它的点,那么可以在这两个点之间加边,那么a就可以直达b,不必经过中点的,那么就转化成了最小不相交路径覆盖。

这道题同样需要输出方案,在残余网络中遍历边处理就可以了。

参考代码 见附录


P2763 试题库问题

我觉得这是一个非常典型的二分图问题。

k 种类型是二分图左侧的 k 个点。

n 个试题是二分图右侧的 n 个点。

如果某试题属于某种类型,就在试题和类型之间连接一条边,容量为 1

要求选出 n u m t 类型的题目,那么就在源点 S t 类型点之间加入一条容量为 n u m 的边。

而每道题目只能使用一次,因此在题目 p 和汇点 T 之间连接一条容量为 1 的边。

然后跑一遍最大流就可以得到答案了。
这里写图片描述

参考代码见附录


P2766 最长不下降子序列问题

这类问题属于动态规划方程已知,对解方案计数的问题。
还有的是要求最短路的数量的问题,我之前有在我的博客里写过,感兴趣的各位可以找找看。

第一问

要求求出最长不下降子序列长度,这个使用动态规划很容易解决,但是我们必须要在这一步中处理出以 a [ i ] 元素为结尾的最长非降子序列的长度数组 f [ i ]

第二问

要求在所有的点都不能重复使用的时候,能找到多少个最长非减子序列。

在网络流处理问题的模型中,如果一个点不能重复利用,那么我们可以将这个点 P 拆成两个点 P s P t ,并由 P s P t 连接一条容量为 1 的边。所有流入这个点的边都与 P s 相连,所有流出这个点的边都与 P t 相连。

在这个题中,我们从 f [ i ] 数组出发,建立一个DAG(有向无环图)。即满足 f [ j ] + 1 = f [ i ] a [ j ] a [ i ] ( j , i ) 我们都建立一条 i j 的容量为 i n f 的边。

然后建立一个超级源点 S ,和超级汇点 T S f [ i ] == m x l e n 的点 i 连边, T f [ i ] == 1 的点 i 连边,容量均为 1
然后按照我上面说的方法拆点。
举例:
3 , 6 , 2 , 5
这个序列的 m x l e n = 2 , f [ ] = 1 , 2 , 1 , 2 ,我们建成的图就是:
这里写图片描述
这样的话,跑一遍最大流就知道有多少条长度为 m x l e n 的不相交的路径了。

第三问

若某个点可以无限用的话,那么这个点就不应该被拆。

对第二问的图稍做修改,如下
这里写图片描述

这里有个小细节 x n x 1 直接相连接的时候,他们之间的边权不能设为 i n f 而应该是 1

另一个小细节就是如果 m x l e n == 1 那么直接输出 n 不需要建图。

参考代码 见附录

P2774 方格取数问题

由这个问题引出的模型是二分图最大点权独立集

二分图点权最大独立集:带点权二分图G中的一个子集V,其中一条边的两个端点不能同时属于V,且V中点权和最大。

二分图最大点权独立集对偶问题的是二分图的最小点权覆盖集,即用最小点权和的点集去覆盖所有的边,解法就是按照如下的方法建图跑最小割。

相邻的格子数不能同时取到,他们两个是互斥的,我们对格子进行黑白染色以后,形成了一张二分图,这个问题转化为求这张二分图的最大点权独立集

这种模型可以采用求最小割的方法来做

S 点向白点连边,容量为白点的权值,从黑点向 T 点连边,容量为黑点的权值。
白点和黑点之间如果有互斥关系,那么从白点向黑点连接一条容量为 i n f 的边,形成一张网络图。

我们观察这张图的最小割,我们发现容量为 i n f 的边一定不能被割掉,那么这条边两端的点 u , v 就必然有 ( S , u ) 被割掉或者是 ( v , T ) 被割掉,被割掉的含义就是这个点的权值我不要了。由于我们得到的是最小割,也就是说我们剩下的点的权值之和一定是最大的,且不互斥的,因此用总的权值和减去最小个的值就是最大权独立集的权值和,也就是本题的答案。

例如下图的答案就是
这里写图片描述
a n s = ( a + b + c + d + e + f ) ( a + b + d + e ) = c + f

参考代码 见附录


P2805 植物大战僵尸

这个问题要引出的模型就更厉害啦!
那就是:
最大权闭合子图
最大权闭合子图
最大权闭合子图
重要的问题说三遍,这个模型很重要,能解决非常多有趣的问题,主要被用来解决在有依赖性条件下的规划问题。

什么是最大权闭合子图?

闭合子图的定义是:在子图中,所有点的出边所指向的节点仍在该子图内。

这里写图片描述

在这张图中,闭合子图有:
{ E } { D , E } { F , E } { A , D , E } { B , D , E } { F , E } { C , F , E }

最大权闭合子图就是找一个最大的权值和的闭合子图。

如何找最大权闭合子图?

我们把这个图进行变形,变形成可以用最小割解决的问题,然后用最小割解决它。
变形方法:如果一个点的权值为正,那么从 S 点向该点连接一条容量为权值的边,如果这个点的权值为负,那么从该点向 T 点连接一条容量为权值绝对值的边,原图中的所有边容量均设置为 i n f 然后跑一遍最小割,原图中正的点权和减去最小割得到的就是最大权闭合子图。

证明这里就不证明了,有兴趣的同学可以去网上搜搜看。

如何解决依赖关系下的选取问题?

如果要选取物品 A 必须要先选取物品 B ,则说物品 A 依赖于物品 B ,反映在图中就是 A B 连有一条边。

选取某个物品可能获利也有可能要付出代价,如果获利则说该物品的权值符号为正,如果获取该物品要付出代价,则说该物品的权值符号为负。

而一种选取方案则必然属于原图中的一个闭合子图,如果不闭合这个选取方案必然非法。

因此,求一种获利最大的选取方案就相当于在求一个最大权闭合子图。

这道题怎么做?

这道题中,僵尸只能从每一行的最右端开始吃,想吃 ( x , y ) 位置的植物必须先吃掉 ( x , y + 1 ) 位置的植物,这样的话,就形成了一个依赖关系 ( x , y ) 依赖于 ( x , y + 1 ) ,写作 ( x , y ) > ( x , y + 1 )
本题中还有另外的一个依赖关系,那就是某植物 A 可以保护其他一些植物 B ,也就是存在依赖关系: ( x B , y B ) > ( x A , y A )
根据这些依赖关系,以及吃掉植物的获利,我们可以建立一张图。

然后在这张图求一个最大权闭合子图,就是我们的答案。

但是我们忽略了一点,就是说可能存在无敌植物,这些植物的依赖关系形成了一个环,这样就不能直接跑最大权闭合子图了,所以,我们第一步要先去掉这张依赖关系图中的。使用什么来去环呢?答案是拓扑排序。

先观察可能出现环的情形:
这里写图片描述

这种情形下,环应该被去掉,因为环代表无敌关系。而依赖于环的点 J 也应该被去掉,因为环被去掉了以后这个依赖关系永远不能成立。

如何去掉环和依赖于环的点?

其实很简单,我们将图中所有的边反向,使用拓扑排序,将所有没有被访问的节点去掉。
这一步的核心操作就在反向

去掉点之后,直接跑最大权闭合子图就可以了。

参考代码 见附录


附录

Dinic网络流模板

以下代码均不含模板

const int inf = 1e9;
const int maxm = 800001;
const int maxn = 10001;
int node,src,dest,edge;
int ver[maxm],flow[maxm],nxt[maxm];
int head[maxn],work[maxn],dis[maxn],q[maxn];
void prepare(int _node,int _src,int _dest)
{
    node=_node,src=_src,dest=_dest;
    for(int i=0; i<node; ++i)head[i]=-1;
    edge=0;
}
void addedge(int u,int v,int c)
{
    ver[edge]=v,flow[edge]=c,nxt[edge]=head[u],head[u]=edge++;
    ver[edge]=u,flow[edge]=0,nxt[edge]=head[v],head[v]=edge++;
}
bool Dinic_bfs()
{
    int i,u,v,l,r=0;
    for(i=0; i<node; ++i)dis[i]=-1;
    dis[q[r++]=src]=0;
    for(l=0; l<r; ++l)
        for(i=head[u=q[l]]; i>=0; i=nxt[i])
            if(flow[i]&&dis[v=ver[i]]<0)
            {
                dis[q[r++]=v]=dis[u]+1;
                if(v==dest)return 1;
            }
    return 0;
}
int Dinic_dfs(int u,int exp)
{
    if(u==dest)return exp;
    for(int &i=work[u],v,tmp; i>=0; i=nxt[i])
        if(flow[i]&&dis[v=ver[i]]==dis[u]+1&&(tmp=Dinic_dfs(v,min(exp,flow[i])))>0)
        {
            flow[i]-=tmp;
            flow[i^1]+=tmp;
            return tmp;
        }
    return 0;
}
int Dinic_flow()
{
    int i,ret=0,delta;
    while(Dinic_bfs())
    {
        for(i=0; i<node; ++i)work[i]=head[i];
        while(delta=Dinic_dfs(src,inf))ret+=delta;
    }
    return ret;
}

P2765

// 网络流模板省去
int n,m = 2000;
int main(){
    scanf("%d",&n);
    prepare(2*m+3,0,2*m+2);
    addedge(2*m+1,2*m+2,n);
    for(int i = 1;;++i){
        addedge(0,2*i-1,1);
        addedge(2*i-1,2*m+1,1,1);
        addedge(2*i,2*m+2,1);
        for(int j = 1;j < i;++j){
            int rt = sqrt(i+j+0.5);
            if(rt*rt == i+j) addedge(2*i-1,2*j,1,2);
        }
        int ans = Dinic_flow();
        if(ans < i){
            printf("%d\n",i-1);
            int vc = 0;
            //生成方案
            vector<int> G[n];
            vector<int> bl(i,-1);
            for(int e = 0;e < edge;e += 2){
                if(flow[e] != 0) continue;
                if(tps[e] == 1){
                    int u = (ver[e+1]+1)/2;
                    G[vc].push_back(u);
                    bl[u] = vc;
                    vc++;
                }
                else if(tps[e] == 2){
                    int u = (ver[e+1]+1)/2;
                    int v = (ver[e]+1)/2;
                    bl[u] = bl[v];
                    G[bl[u]].push_back(u);
                }
            }
            for(int i = 0;i < n;++i){
                for(auto v : G[i]) printf("%d ",v);
                puts("");
            }
            break;
        }
    }
    return 0;
}

P2764

int pnxt[maxn];
int deg[maxn];
int n,m;
int main(){
    cin>>n>>m;
    prepare(2*n+2,0,2*n+1);
    for(int i = 0;i < m;++i){
        int u,v;
        scanf("%d%d",&u,&v);
        addedge(u,n+v,1);
    }
    for(int i = 1;i <= n;++i) addedge(0,i,1),addedge(i+n,2*n+1,1);
    int ans = Dinic_flow();
    for(int e = 0;e < 2*m;e += 2){
        if(flow[e] == 0){
            int u = ver[e+1];
            int v = ver[e] - n;
            pnxt[u] = v;
            deg[v]++;
        }
    }
    for(int i = 1;i <= n;++i){
        if(deg[i]) continue;
        int v = i;
        while(v){
            printf("%d ",v);
            v = pnxt[v];
        }
        puts("");
    }
    cout<<n-ans<<endl;
    return 0;
}

P2763

int n,k;
int kn[30];
int main(){
    cin>>k>>n;
    prepare(1+k+n+1,0,1+k+n);
    for(int i = 1;i <= k;++i) cin>>kn[i];
    for(int i = 1;i <= k;++i) addedge(0,i,kn[i]);
    for(int i = 1;i <= n;++i){
        int num;scanf("%d",&num);
        for(int j = 1;j <= num;++j){
            int tt;scanf("%d",&tt);
            addedge(tt,i+k,1,1);
        }
    }
    for(int i = 1;i <= n;++i) addedge(i+k,1+k+n,1);
    int ans = Dinic_flow();
    vector<int> G[30];
    for(int e = 0;e < edge;++e){
        if(flow[e] == 0 && tps[e]){
            int u = ver[e+1];
            int v = ver[e]-k;
            G[u].push_back(v);
        }
    }
    for(int i = 1;i <= k;++i){
        printf("%d: ",i);
        for(auto v : G[i]) printf("%d ",v);
        puts("");
    }

    return 0;
}

P2766

int n,dp[507],f[507],a[507];
int main(){
    memset(dp,0x3f,sizeof(dp));
    cin>>n;
    for(int i = 1;i <= n;++i){
        int tmp;
        cin>>tmp;
        a[i] = tmp;
        int loc = upper_bound(dp+1,dp+1+n,tmp) - dp;
        f[i] = loc;
        dp[loc] = tmp;
    }
    int s = lower_bound(dp+1,dp+1+n,0x3f3f3f3f)-dp-1;
    cout<<s<<endl;
    prepare(2*n+2,0,2*n+1);
    for(int i = n;i >= 1;--i){
        addedge(i,i+n,1);
        if(f[i] == s) addedge(0,i,505);
        if(f[i] == 1) addedge(i+n,2*n+1,505);
        for(int j = i-1;j >= 1;--j){
            if(f[j]+1 == f[i] && a[j] <= a[i]) addedge(i+n,j,505);
        }
    }
    cout<<Dinic_flow()<<endl;
    if(s == 1) {
        return 0*printf("%d\n",n);
    }
    prepare(2*n+2,0,2*n+1);
    for(int i = n;i >= 1;--i){
        addedge(i,i+n,1);
        if(f[i] == s){
            if(i == n) addedge(0,i+n,505);
            else addedge(0,i,505);
        }    
        if(f[i] == 1){
            if(i == 1) addedge(i,2*n+1,505);
            else addedge(i+n,2*n+1,505);
        }
        for(int j = i-1;j >= 1;--j){
            if(j == 1 && i == n) continue;
            if(f[j]+1 == f[i] && a[j] <= a[i]) addedge(i+n,j,505);
        }
    }
    int ans = Dinic_flow();
    if(f[1] + 1 == f[n]) ans ++;
    cout<<ans<<endl;
}

P2774

int n,m;
int a[101][101];
int dx[] = {-1,1,0,0};
int dy[] = {0,0,-1,1};
int main(){
    long long sum = 0;
    cin>>m>>n;
    for(int i = 1;i <= m;++i){
        for(int j = 1;j <= n;++j) {
            scanf("%d",&a[i][j]);
            sum += a[i][j];
        }
    }

    prepare(n*m+2,0,n*m+1);

    for(int i = 1;i <= m;++i){
        for(int j = 1;j <= n;++j){
            int u = (i-1)*n+j;
            if((i+j)%2 == 0) addedge(0,u,a[i][j]);
            else addedge(u,n*m+1,a[i][j]);

            if((i+j)%2 == 0)
            for(int t = 0;t < 4;++t){
                int nx = i + dx[t],ny = j + dy[t];
                if(nx < 1 || nx > m || ny < 1 || ny > n) continue;
                int v = (nx-1)*n+ny;
                addedge(u,v,inf);
            }
        }
    }

    cout<<sum - Dinic_flow()<<endl;
    return 0;
}

P2805

typedef pair<int,int> pii;
int a[100][100];
vector<pii> ps[100][100];
int n,m;
long long sum;
#define pr(x) cout<<#x<<":"<<x<<endl
int main(){
    //freopen("ts3.txt","r",stdin);
    cin>>n>>m;
    prepare(n*m+2,0,n*m+1);
    for(int i = 1;i <= n;++i){
        for(int j = 1;j <= m;++j){
            int num;
            scanf("%d%d",&a[i][j],&num);
            for(int t = 0;t < num;++t){
                int x,y;scanf("%d%d",&x,&y);
                x++,y++;
                ps[i][j].push_back(make_pair(x,y));
            }
            if(j != 1) ps[i][j].push_back(make_pair(i,j-1));
        }
    }
    void toposort();
    toposort();
    long long ans = (long long)Dinic_flow();
    cout<<sum-ans<<endl;
}
int deg[30][31],vis[31][31],f[31][31];
void toposort(){
    queue<pii> Q;
    for(int ux = 1;ux <= n;++ux) for(int uy = 1;uy <= m;++uy){
        for(auto p : ps[ux][uy]) { deg[p.first][p.second] ++;}
    }
    for(int ux = 1;ux <= n;++ux) for(int uy = 1;uy <= m;++uy){
        if(!deg[ux][uy]) Q.push(make_pair(ux,uy));
    }

    while(!Q.empty()){
        pii p = Q.front();Q.pop();
        sum += max(0,a[p.first][p.second]); 
        vis[p.first][p.second] = 1;
        for(auto p2:ps[p.first][p.second]){
            deg[p2.first][p2.second]--;
            if(deg[p2.first][p2.second]==0) Q.push(p2);
        }
    }

    for(int ux = 1;ux <= n;++ux) for(int uy = 1;uy <= m;++uy){
        if(!vis[ux][uy]) continue;
        for(auto p : ps[ux][uy]) {
            if(vis[p.first][p.second]) addedge((p.first-1)*m+p.second,(ux-1)*m+uy,inf);
        }
        if(a[ux][uy] >= 0) addedge(0,(ux-1)*m+uy,a[ux][uy]);
        else addedge((ux-1)*m+uy,n*m+1,-a[ux][uy]);
    }

}

原创:西安交大 蔡少斐

猜你喜欢

转载自blog.csdn.net/weixin_37517391/article/details/80066502