网络流——最大权闭合子图

也许看到“最大权闭合子图”这个词许多人就懵逼了。

一开始我也是这样,那年我在GDKOI2016上遇见了宝藏。


定义:

一个子图(点集), 如果它的所有的出边都在这个子图当中,那么它就是闭合子图。
点权和最大的闭合子图就是最大闭合子图。


简单说就是有一些点,每个点有一些点权(正负都有),要选一个点,就必须要选它所连向的点。

有可能连成一条链,像这样:x->y->z->…

求合法的点集的最大的点权和。


讲讲怎么构图:

设s为源点,t为汇点。

使s连向所有的正权点(非负权点),边权为点权。

使所有非负权点(负权点)连向t,边权为点权的绝对值。

若需要选y才能选x,连一条由x到y的边,边权是∞。

最大点权和=正权点和-最小割


证明:

我们知道一个割会把图分成两个部分,一部分是S能走到的,另一部分是能走到T的。
我们设这两个集合为A,B。

A0,B0分别表示A,B中原来点权是负数的点集, A1,B1分别表示A,B中点权原来是正数的点集。

由于中间的边是max的,割的一定是和S,T相连的边,这个叫简单割。
所以割的大小 = |A0| +B1
绝对值表示把负的变成了正的。

A,B一定是最大权闭合子图
A的点权和 = A1 - |A0|
A的点权和 + 割的大小 = A1 + B1 = 全图中的正权点和
这是一个定值, 要使A的点权和最大, 就要使割最小,就是最小割,

至此得证。


参考资料:

胡伯涛《最小割模型在信息学竞赛中的应用》


裸题:GDKOI2006 破解密文,读者可以去查一下。

Code:

#include
#define min(a,b) ((a)<(b)?(a):(b))
#define fo(i,x,y) for(int i=x;i<=y;i++)
using namespace std;
const int maxn=505;
struct node{
    int to,next,flow;
}e[maxn*maxn*2]; 
int tot=1,ans,n,m,s,t,p[maxn],cur[maxn],co[maxn],d[maxn],final[maxn];
void link(int x,int y,int z){
    e[++tot].to=y,e[tot].next=final[x],e[tot].flow=z,final[x]=tot;
    e[++tot].to=x,e[tot].next=final[y],e[tot].flow=0,final[y]=tot;
}
int dg(int x,int flow){
    if(x==t) return flow;
    int use=0;
    for(int i=cur[x];i;i=e[i].next){
        cur[x]=i;
        if(e[i].flow>0 && d[e[i].to]+1==d[x]){
            int c=dg(e[i].to,min(flow-use,e[i].flow));
            use+=c,e[i].flow-=c,e[i^1].flow+=c;
            if(flow==use) return use;
        }
    }
    cur[x]=final[x];
    if(!(--co[d[x]])) d[0]=t;
    ++co[++d[x]];
    return use;
}
int main(){
    scanf("%d",&n);
    s=n+1,t=n+2;
    fo(i,1,n){
        scanf("%d %d",&p[i],&m);
        if(p[i]>0)
            ans+=p[i],link(s,i,p[i]); else link(i,t,-p[i]);
        fo(j,1,m){
            int y; scanf("%d",&y);
            link(i,y,1<<30);
        }
    }
    co[0]=t;
    for(;d[0]s,1<<30);
    printf("%d",ans);
}

你可能感兴趣的:(网络流,模版)