[jzoj 4528] [GDOI2019模拟2019.3.26] 要换换名字 (最大权闭合子图)

题目链接:

https://jzoj.net/senior/#contest/show/2683/0

题目:

题解:

不妨枚举一个点,让两颗树都以这个点为根,求联通块要么点数为$0$,要么包括根(即联通块必须从根开始)

考虑一个不是根的点$x$,若$x$在联通块以内,要保证联通块的连通性,那么从$x$的父亲要在联通块以内

这种一个点选了就必须要选另一个点的问题是典型的最大权闭合子图模型

做法如下

设$s$为源点,$t$为汇点。

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

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

若需要选$y$就要选$x$,连一条由$x$到$y$的边,边权是$∞$(其实这个地方我一直有疑惑,有的认为是连从$x$到$y$的边,如果有读者知道真相麻烦告诉我)

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

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

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

由于中间的边是$∞$的,割的一定是和$S$,$T$相连的边,这个叫简单割。
所以割的大小 $= |A0| +B1$ 

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

至此得证。

代码:

#include<algorithm>
#include<cstring>
#include<cstdio>
#include<iostream>
#include<cmath>
#include<queue>
using namespace std;

const int N=600+15; 
const int inf=1e9+7;
int n,tot1,tot2,cnt;
int a[N],h1[N],h2[N],head[N],dep[N],nowhead[N];
struct EDGE
{
    int to,nxt;
}e1[N<<1],e2[N<<1];
struct node
{
    int to,nxt;
    int flow;
}edge[N<<2];
inline int read()
{
    char ch=getchar();int s=0,f=1;
    while (ch<'0'||ch>'9') {if (ch=='-') f=-1;ch=getchar();}
    while (ch>='0'&&ch<='9') {s=(s<<3)+(s<<1)+ch-'0';ch=getchar();}
    return s*f;
}
void link(int u,int v,int w)
{
    edge[++cnt]=(node){v,head[u],w};head[u]=cnt;
    edge[++cnt]=(node){u,head[v],0};head[v]=cnt;
}
void add1(int u,int v)
{
    e1[++tot1]=(EDGE){v,h1[u]};
    h1[u]=tot1;
}
void add2(int u,int v)
{
    e2[++tot2]=(EDGE){v,h2[u]};
    h2[u]=tot2;
}
void init()
{
    cnt=1;
    memset(head,-1,sizeof(head));
}
void dfs1(int x,int pre)
{
    if (pre) link(x,pre,inf);
    for (int i=h1[x];i;i=e1[i].nxt)
    {
        int y=e1[i].to;
        if (y==pre) continue;
        dfs1(y,x);
    }
}
void dfs2(int x,int pre)
{
    if (pre) link(x,pre,inf);
    for (int i=h2[x];i;i=e2[i].nxt)
    {
        int y=e2[i].to;
        if (y==pre) continue;
        dfs2(y,x);
    }
}
int S,T;
queue <int> q;
int bfs()
{
    memset(dep,0,sizeof(dep));
    while (!q.empty()) q.pop(); 
    dep[S]=1;
    q.push(S);
    while (!q.empty())
    {
        int k=q.front();q.pop();
        for (int i=head[k];i!=-1;i=edge[i].nxt)
        if (edge[i].flow&&!dep[edge[i].to])
        {
            dep[edge[i].to]=dep[k]+1;
            q.push(edge[i].to);
        }
    }
    return dep[T];
}
int dfs(int x,int a)
{
    if (x==T||!a) return a;
    int flow=0,f;
    for (int &i=nowhead[x];i!=-1;i=edge[i].nxt)
    if (dep[x]+1==dep[edge[i].to]&&(f=dfs(edge[i].to,min(a,edge[i].flow)))>0)
    {
        edge[i].flow-=f;
        edge[i^1].flow+=f;
        flow+=f;
        a-=f;
        if (a==0) break;
    }
    return flow;
}
int dinic()
{
    int ans=0;
    while (bfs())
    {    
        copy(head,head+n+3,nowhead);//这行优化重要无比 
        ans+=dfs(S,inf);
    } 
    return ans;
}
int main()
{
    //freopen("theory.in","r",stdin);
    int W=read();
    while (W--)
    {
        tot1=0;tot2=0;
        int sum=0;
        n=read();
        S=n+1,T=n+2;
        for (int i=1;i<=n;i++) 
        {
            h1[i]=0;h2[i]=0;
            a[i]=read();
            if (a[i]>0) sum+=a[i];
        }
        for (int i=1;i<n;i++)
        {
            int u=read(),v=read();
            add1(u,v);add1(v,u);
        }
        for (int i=1;i<n;i++)
        {
            int u=read(),v=read();
            add2(u,v);add2(v,u);
        }
        int res=0;
        for (int i=1;i<=n;i++)
        {
            init();
            dfs1(i,0);dfs2(i,0);
            for (int j=1;j<=n;j++) if (a[j]>=0) link(S,j,a[j]);else link(j,T,-a[j]);
            res=max(res,sum-dinic());
        }
        printf("%d
",res);
    }
    return 0;
}
原文地址:https://www.cnblogs.com/xxzh/p/10604083.html