[51nod1325]两棵树的问题

Description

给出两棵n个点的树,每个点有对应的点权(可正可负)。你需要选择一个点集,使得:
1:在两棵树中这个点集都是一个联通快
2:这个点集中所有点的点权和最大。
求这个最大值。
n<=50

Solution

首先让我们来思考一下一棵树怎么做。
那么就是一个显然的树形dp了。
枚举根节点,变成有根树然后乱搞。
但是发现放到两棵树上就无法做了,怎么办呢?
我们发现,如果我们是有根树且必须要包含根节点,那么我们选择了一个点,那么它的父亲也一定要选。
这就是有依赖关系了。。。
最大权闭合子图!!
枚举根节点,转换成有根树,每个点依赖于它在两棵树中的父亲,然后跑最大权闭合子图。。。
然后就没有了。。。

Code

#include <cstdio>
#include <cstring>
#include <algorithm>
#define fo(i,a,b) for(int i=a;i<=b;i++)
#define rep(i,a,z) for(int i=la[z][a];i;i=ne[z][i])
#define rp(i,a) for(int i=last[a];i;i=next[i])
using namespace std;
const int N=55,inf=0x7fffffff;
int n,S,T,l[2],tot,x,y,sum,ans,dis[N],d[N],v[N];
int t[2][N*4],ne[2][N*4],la[2][N],to[N*6],next[N*6],f[N*6],last[N];
void add(int x,int y,int z) {
    t[z][++l[z]]=y;ne[z][l[z]]=la[z][x];la[z][x]=l[z];
}
void line(int x,int y,int z) {
    to[++tot]=y;f[tot]=z;next[tot]=last[x];last[x]=tot;
    to[++tot]=x;f[tot]=0;next[tot]=last[y];last[y]=tot;
}
void dfs(int x,int y,int z) {
    if (y) line(x,y,inf);
    rep(i,x,z) if (t[z][i]!=y) dfs(t[z][i],x,z);
}
void link(int x) {
    tot=1;memset(last,0,sizeof(last));
    dfs(x,0,0);dfs(x,0,1);
    fo(i,1,n) if (v[i]>0) sum+=v[i],line(S,i,v[i]);
    else line(i,T,-v[i]);
}
bool bfs() {
    memset(dis,0,sizeof(dis));dis[S]=1;
    int i=0,j=1;d[1]=S;
    while (i<j) rp(k,d[++i]) 
        if (!dis[to[k]]&&f[k]) dis[to[k]]=dis[d[i]]+1,d[++j]=to[k];
    return dis[T];
}
int dinic(int x,int y) {
    if (x==T) return y;
    int now=0;
    rp(i,x) if (dis[to[i]]==dis[x]+1&&f[i]) {
        int k=dinic(to[i],min(y,f[i]));
        y-=k;now+=k;f[i]-=k;f[i^1]+=k;
        if (!y) break;
    }
    if (!now) dis[x]=-1;
    return now;
}
int main() {
    scanf("%d",&n);S=0;T=n+1;
    fo(i,1,n) scanf("%d",&v[i]);
    fo(i,1,n-1) scanf("%d%d",&x,&y),x++,y++,add(x,y,0),add(y,x,0);
    fo(i,1,n-1) scanf("%d%d",&x,&y),x++,y++,add(x,y,1),add(y,x,1);
    fo(i,1,n) {
        sum=0;link(i);
        while (bfs()) sum-=dinic(S,inf);
        ans=max(ans,sum);
    }
    printf("%d\n",ans);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值