100003. Tree

题目大意

给定一棵以1为根 n 个节点的树,树上每条边有一个覆盖的限制次数di。现在给出树上的 m 条链,每条链有一个价值ci。现在可以从中选出一些链,在满足覆盖次数的限制下,求最大的价值。
T 组数据。

Data Constraint
T1000,m1000,n100000

题解

考虑网络流,对于每一条树边 i (u,v),deepu<deepv u v连一条流量为 di 费用为 0 的边。
对于每一条链i,uv,deepu<deepv v u连一条流量为 1 费用为ci的边。
然后跑最大费用循环流(无源汇最大费用流)。


最大费用循环流

先将所有费用>=0的边强制满流,费用记为 ans
degi=iniouti in 为入流之和, out 为出流之和。
对于所有 i(degi<0) S i连一条流量为 degi 费用为 0 的边。
对于所有i(degi>0) i T连一条流量为 degi 费用为 0 的边。
然后从S T 跑一遍最小费用流,本质上就是为了保证流量平衡模拟退流过程。
最后的最大费用就是ansmincost


SRC

#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<vector>
using namespace std ;

#define N 500000 + 10
#define M 10000 + 10
typedef long long ll ;
struct Path {
    int u , v , c ;
} P[N] ;
struct Edge {
    int u , v , d ;
} E[N] ;

vector < int > G[N] ;

int Node[10*N] , Next[10*N] , C[10*N] , Cost[10*N] , Head[M] , tot ;
int D[10*N] , Pre[N] , vis[N] ;
ll Dist[M] ;
int Deep[N] , fa[N] , Deg[M] ;
int Case , n , m ;
int S , T ;
ll ans ;

bool cmp( Path a , Path b ) { return a.c > b.c || ( a.c == b.c && Deep[a.u] - Deep[a.v] < Deep[b.u] - Deep[b.v] ) ; }

void DFS( int x , int F ) {
    fa[x] = F ;
    for (int p = 0 ; p < G[x].size() ; p ++ ) {
        if ( G[x][p] == F ) continue ;
        Deep[G[x][p]] = Deep[x] + 1 ;
        DFS( G[x][p] , x ) ;
    }
}

void link( int u , int v , int w , int cost ) {
    Node[++tot] = v , Next[tot] = Head[u] , C[tot] = w , Cost[tot] = cost , Head[u] = tot ;
    Node[++tot] = u , Next[tot] = Head[v] , C[tot] = 0 , Cost[tot] = -cost , Head[v] = tot ;
}

bool SPFA( int st ) {
    memset( Dist , 63 , sizeof(Dist) ) ;
    int i = 0 , j = 1 ;
    D[1] = st ;
    vis[st] = 1 ;
    Dist[st] = 0 ;
    while ( i < j ) {
        i ++ ;
        int now = D[i] ;
        for (int p = Head[now] ; p ; p = Next[p] ) {
            if ( !C[p] ) continue ;
            if ( Dist[now] + Cost[p] < Dist[Node[p]] ) {
                Dist[Node[p]] = Dist[now] + Cost[p] ;
                Pre[Node[p]] = p ;
                if ( !vis[Node[p]] ) {
                    vis[Node[p]] = 1 ;
                    D[++j] = Node[p] ;
                    if ( Dist[D[j]] < Dist[D[i+1]] ) swap( D[i+1] , D[j] ) ;
                }
            }
        }
        vis[now] = 0 ;
    }
    return Dist[T] < Dist[T+1] ;
}

ll MinCost() {
    ll ret = 0 ;
    while ( SPFA(S) ) {
        int Minv = 0x7FFFFFFF ;
        for (int x = T ; x != S ; x = Node[Pre[x]^1] ) Minv = min( Minv , C[Pre[x]] ) ;
        ret += (ll)Minv * Dist[T] ;
        for (int x = T ; x != S ; x = Node[Pre[x]^1] ) {
            C[Pre[x]] -= Minv ;
            C[Pre[x]^1] += Minv ;
        }
    }
    return ret ;
}

int main() {
    freopen( "tree.in" , "r" , stdin ) ;
    freopen( "tree.out" , "w" , stdout ) ;
    scanf( "%d" , &Case ) ;
    while ( Case -- ) {
        ans = 0 ;
        tot = 1 ;
        memset( Deg , 0 , sizeof(Deg) ) ;
        memset( Head , 0 , sizeof(Head) ) ;
        scanf( "%d%d" , &n , &m ) ;
        S = 0 , T = n + 1 ;
        for (int i = 1 ; i <= n ; i ++ ) G[i].clear() ;
        for (int i = 1 ; i < n ; i ++ ) {
            scanf( "%d%d%d" , &E[i].u , &E[i].v , &E[i].d ) ;
            G[E[i].u].push_back( E[i].v ) ;
            G[E[i].v].push_back( E[i].u ) ;
        }
        Deep[1] = 1 ;
        DFS( 1 , 0 ) ;
        for (int i = 1 ; i < n ; i ++ ) {
            if ( Deep[E[i].u] > Deep[E[i].v] ) swap( E[i].u , E[i].v ) ;
            link( E[i].u , E[i].v , E[i].d , 0 ) ;
            Deg[E[i].u] -= E[i].d , Deg[E[i].v] += E[i].d ;
        }
        for (int i = 1 ; i <= m ; i ++ ) {
            scanf( "%d%d%d" , &P[i].u , &P[i].v , &P[i].c ) ;
            if ( Deep[P[i].u] < Deep[P[i].v] ) swap( P[i].u , P[i].v ) ;
            ans += P[i].c ;
            Deg[P[i].v] ++ , Deg[P[i].u] -- ;
            link( P[i].u , P[i].v , 1 , P[i].c ) ;
        }
        for (int i = 1 ; i <= n ; i ++ ) {
            if ( Deg[i] < 0 ) link( S , i , -Deg[i] , 0 ) ;
            else link( i , T , Deg[i] , 0 ) ;
        }
        ll tmp = MinCost() ;
        printf( "%lld\n" , ans - tmp ) ;
    }
    return 0 ;
}

以上.

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值