最大权闭合子图 - [求最大点权的闭合子图](算法分析)

引言·什么是最大权闭合子图

先讲闭合子图的概念,就是一幅图中每个点,以及每个点的出边的点都在这幅图中,也就是这幅图中的所有点的出边都是指向子图内部的。

最大权闭合子图:在所有的闭合子图中,它所包含的子图的点的点权之和是最大的。

上述的问题,都是基于将权值放在点上去考虑,现在,我们想要去求解最大权闭合子图,需要把点权考虑成边权(边的流量)来计算。

证明·推导·分析过程

首先有一个有向连通图,每个点带有一个权值,例如:

最大权闭合子图 - [求最大点权的闭合子图](算法分析)_第1张图片

 

Tips:按权值的正负连接到s和t:从贪心的角度来看,最大闭合子图的起点必然是正权值点,但是不能保证起点是哪个正权值点,而最小割是求S到T的最小割,于是乎,如果说有的正权值是作为闭合子图的末尾,实际上并不会影响到答案,这些在下文有阐述到。

此时,构建一个超级源点s,一个超级汇点t,所有的点按权值的正负连接到s和t上,其余边的边权设置为INF,转换成一个边权值有向图,如下图:

最大权闭合子图 - [求最大点权的闭合子图](算法分析)_第2张图片

(注:点权为0的点可以忽略,对结果没有影响)

 

这时,可以得到小结论:

 

①该带边权有向图的关于s-t最小割,是简单割;

  简单割:割集中所有的边,都与s或t相连接。

这里可以简单的想想,除了链接S和链接T的边,其余的边都是INF边,我们是不可能割INF边的。

  显然的,因为不与s,t相连的边,权值都是INF,最小割不可能割在INF的边上;

  这早在文章http://www.cnblogs.com/dilthey/p/7401563.html中就有出现提到过,实际上,这个带边权有向图就是二分图;

 

②该图中的每一个简单割产生的两个子图,我们记含有点s的是图S,含有点t的是图T,则图S是闭合图;

  闭合图:在一个图中,我们选取一些点构成集合,若集合中任意点连接的任意出弧,所指向的终点也在V中,则这个集合以及所有这些边构成闭合图。

简单推理:因为中间的边(原边)的流量都设置为了INF,所以肯定会走到底的。

  证明:简单割内不包含边权为INF的边,即不含有连通两个图的边(除了连接在t点上的边之外);

     即,图S中没有边与图T连通,那么,所有的边都只能连接在图S之内,即为闭合图。

  样例:

     最大权闭合子图 - [求最大点权的闭合子图](算法分析)_第3张图片     最大权闭合子图 - [求最大点权的闭合子图](算法分析)_第4张图片

 

③最小割产生的图S和图T,图S为最大权闭合子图;

  最大权闭合子图:在整个图中,有多个子图是满足闭合图的条件的,其中点权值之和最大的,为最大权闭合子图;

  因为割集中所有的边,不是连接在s上,就是连接在t上;

  我们记割集中,所有连接在s上的边的权值和为x1,所有连接在t上的边的权值和为x2,而割集中所有边权值和为X=x1+x2;

X = X1 + X2:是因为我们的割边,要么是链接S处,要么是链接T处。

  又,记图S中所有点的权值和为W,记其中正权值之和为w1,负权值之和为 - w2,故W = w1 - w2;

  而 W + X = w1 - w2 + x1 + x2,由于x2 = w2

X2 = W2: X2是T处的割边,而W2是图S的负权值点权和,又有负权值点是直接与T相连的,所以X2 = W2。

    (因为图S中所有负权值的点,必然连接到t点,而图S必然要与t分割开;故割集中,“连接在t点上的边权值和”就是“图S中所有负权值点的权值之和,取负”)

  因而W + X = w1 + x1;

  而显然的,w1 + x1是整个图中所有正权值之和,记为SUM;

  故W = SUM - X,即 “图S中所有点的权值和” = “整个图中所有正权值之和”  - “割集中所有边权值和”;

  然后,因为SUM为定值,只要我们取最小割,则“图S中所有点的权值和”就是最大的,即此时图S为图S为最大权闭合子图;

 

求解方法

最后,我们就有了求解这类问题的完整思路:

  ①先记录整个图中,所有正点权值的和;

  ②建立对应流网络,求最大流,最大流在数值上等于最小割,故我们得到了流网络的s-t最小割;

  ③“所有正点权值的和”减去“s-t最小割”,即得最大权闭合子图的权值和。

 

参考资料一

参考资料二

 

例题讲解

最经典的例题就是太空飞行计划问题

首先,这里强调的是从S出发。所以,其实暗示了我们只能采取一些合适的最大流求最小割的算法,例如说ISAP就是不可以的,它的本质是从T往S的割集,例如说这样的情况就会出现问题:

最大权闭合子图 - [求最大点权的闭合子图](算法分析)_第5张图片

从ISAP的角度出发,会使得它的高度变化变成:

最大权闭合子图 - [求最大点权的闭合子图](算法分析)_第6张图片

如果说,我们利用T->5->2->S,以及T->4->2->S将“S->2”这条边的流量给流完了,实际上确实不合理的这样就变成了把T这端的变成了我们想要的类型,和我们上面的推导的算法过程是不符合的。

  所以,我们求解的方法仍然是Dinic()来跑最大流求最小割,最后所要的点还是在S端的链接的点。

2 3
3 1 2
2 2 3
1 2 1
ans:
1 2
1 2 3
1
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#define lowbit(x) ( x&(-x) )
#define pi 3.141592653589793
#define e 2.718281828459045
#define INF 0x3f3f3f3f
#define HalF (l + r)>>1
#define lsn rt<<1
#define rsn rt<<1|1
#define Lson lsn, l, mid
#define Rson rsn, mid+1, r
#define QL Lson, ql, qr
#define QR Rson, ql, qr
#define myself rt, l, r
using namespace std;
typedef unsigned long long ull;
typedef unsigned int uit;
typedef long long ll;
bool The_end;
inline int read()
{
    int x = 0; The_end = false;
    char c = getchar();
    while(c < '0' || c > '9') c = getchar();
    while(c >= '0' && c <= '9')
    {
        x = x * 10 + c - '0';
        c = getchar();
    }
    if(c == '\n' || c == '\r') The_end = true;
    return x;
}
const int maxN = 2e2 + 7, maxM = 6e3 + 7;
int M, N, head[maxN], cnt;
int get_money[55], need_cost[55];
struct Eddge
{
    int nex, to; ll flow;
    Eddge(int a=-1, int b=0, ll c=0):nex(a), to(b), flow(c) {}
}edge[maxM];
inline void addEddge(int u, int v, ll w)
{
    edge[cnt] = Eddge(head[u], v, w);
    head[u] = cnt++;
}
inline void _add(int u, int v, ll w) { addEddge(u, v, w); addEddge(v, u, 0); }

struct Max_Flow
{
    int deep[maxN], cur[maxN], S, T, node;
    queue Q;
    inline bool bfs()
    {
        while(!Q.empty()) Q.pop();
        for(int i=0; i<=node; i++) deep[i] = 0;
        deep[S] = 1;
        Q.push(S);
        int u;
        while(!Q.empty())
        {
            u = Q.front(); Q.pop();
            for(int i=head[u], v, f; ~i; i=edge[i].nex)
            {
                v = edge[i].to; f = edge[i].flow;
                if(f && !deep[v])
                {
                    deep[v] = deep[u] + 1;
                    Q.push(v);
                }
            }
        }
        return deep[T];
    }
    int dfs(int u, int dist)
    {
        if(u == T) return dist;
        for(int &i=cur[u], v, f, di; ~i; i=edge[i].nex)
        {
            v = edge[i].to; f = edge[i].flow;
            if(f && deep[v] == deep[u] + 1)
            {
                di = dfs(v, min(dist, f));
                if(di)
                {
                    edge[i].flow -= di; edge[i ^ 1].flow += di;
                    return di;
                }
            }
        }
        return 0;
    }
    inline int Dinic()
    {
        int ans = 0, tmp;
        while(bfs())
        {
            for(int i=0; i<=node; i++) cur[i] = head[i];
            while((tmp = dfs(S, INF))) ans += tmp;
        }
        return ans;
    }
} mf;
inline void init()
{
    cnt = 0; mf.S = 0; mf.T = N + M + 1; mf.node = N + M + 2;
    for(int i=0; i<=mf.node; i++) head[i] = -1;
}
vector vt[55];
bool used[maxN] = {false};
int main()
{
    scanf("%d%d", &M, &N);
    init();
    ll sum = 0;
    for(int i=1, x = 0; i<=M; i++)
    {
        x = 0;
        The_end = false;
        while(!The_end)
        {
            if(!x)
            {
                x = read(); get_money[i] = x; sum += x;
                _add(mf.S, i, x);
            }
            else
            {
                x = read(); vt[i].push_back(x);
                _add(i, x + M, INF);
            }
        }
    }
    for(int i=1; i<=N; i++)
    {
        scanf("%d", &need_cost[i]);
        _add(M + i, mf.T, need_cost[i]);
    }
    ll tmp = mf.Dinic();
    for(int i=head[mf.S], v; ~i; i=edge[i].nex)
    {
        v = edge[i].to;
        if(mf.deep[v])
        {
            printf("%d ", v);
            for(auto x : vt[v]) used[x] = true;
        }
    }
    if(tmp < sum) printf("\n");
    for(int i=1; i<=N; i++) if(used[i]) printf("%d ", i);
    if(tmp < sum) printf("\n");
    printf("%lld\n", sum - tmp);
    return 0;
}

 

你可能感兴趣的:(图论,网络流)