【洛谷】 3264 [JLOI2015] 管道连接

前言:


如果还不知道斯坦纳树的童鞋可以看这两篇博客:

我的: Mine
我一开始学习的: Which I Have Studied

这道题,在我学习斯坦纳树之前就翻到了,是在洛谷上搜状压的时候看到的。那个时候还不知道斯坦纳树是个什么玩意,不过马上进行了学习。

然而学习了之后也没有什么卵用,发现并不只是斯坦纳树这么简单呐!


题解:


由于存在相同频率之间的连通,所以和一般的斯坦纳树是不同的(发现斯坦纳树所指定的结点频率是相同的),需要二次DP。

首先跑一遍斯坦纳树的板子,这就求出了 以某一个结点为树根并加入一些边使某几个点连通的最小值,那么我们可以用这个去更新另一个 DP 数组:Ans(S)。

方程:Ans(S)= minx(Ans(S),dp(i,S));


定义 DP 数组 Ans (S),表示使状态 S 中所有点连通的最小费用,注意,这个 DP 的过程是有条件的:

首先枚举 S 集合(指定点集合),这个将这个状态 S 进行更新的条件是 S 必须:对于一个频率,要么包含该频率中的所有指定点,要么不包含于这个频率的任意结点!

接着枚举 S 的子集,这个子集 S1 也是要有条件的,也是 必须:对于一个频率,要么包含该频率中的所有指定点,要么不包含于这个频率的任意结点!

得到DP方程:Ans(S) = min(Ans(S),Ans(S1)+Ans(S^S1));

这样最后的答案就是 Ans(1<<(p)-1);

为什么要这样做呢?这样做不是和斯坦纳树中的 dp(i,S)数组重复了吗?

然而却有很大的区别,这样的 Ans(S)是使得 S 中相同的频率连通,而不是 S 中的所有点都连通!因为用于更新 S 的子集也满足这个条件,而 子集子集对于 S 的补集没有联系,换句话说,两个子集内结点的连通互补干涉。

而加上频率的限制:


可以看出我们完全不用连边 2-5,因为这条边不改变相同频率的连通性!


所以带条件 Ans(S)经过DP更新后一定是最优解,并且保证没有额外边。


代码:


#include <bits/stdc++.h>

std :: queue < int >  q ;

const  int  N = 2000 + 5 ;

struct  node {
    int  id , clr ;
}
dot [ 20 ] ;

int  head [ N << 3 ] , nxt [ N << 3 ] , dis [ N << 3 ] , to [ N << 3 ] , cn ;
int  dp [ N ] [ 1 << 11 ] , ans [ 1 << 11 ] , cnt [ 30 ] , sum [ 30 ] , n , m , p , S , x , y , w , inf ;
bool  vis [ N ] , ck [ 1 << 11 ] ;

int  minx ( int  a , int  b ) {
    return  a > b ? b : a ;
}

void  create ( int  u , int  v , int  d ) {
    cn ++ ;
    to [ cn ] = v ;
    dis [ cn ] = d ;
    nxt [ cn ] = head [ u ] ;
    head [ u ] = cn ;
}

void  spfa ( int  S ) {
    for ( int  i = 1 ; i <= n ; i ++ )
        if ( dp [ i ] [ S ] < inf )
            vis [ i ] = true , q . push ( i ) ;
    while ( ! q . empty ( ) ) {
        int  v , tmp = q . front ( ) ;
        q . pop ( ) ; vis [ tmp ] = false ;
        for ( int  i = head [ tmp ] ; i ; i = nxt [ i ] ) {
            v = to [ i ] ;
            if ( dp [ v ] [ S ] > dp [ tmp ] [ S ] + dis [ i ] ) {
                dp [ v ] [ S ] = dp [ tmp ] [ S ] + dis [ i ] ;
                if ( ! vis [ v ] ) {
                    vis [ v ] = true ;
                    q . push ( v ) ;
                }
            }
        }
  	}
}

bool  check ( int  S ) {
    memset ( cnt , 0 , sizeof ( cnt ) ) ;
    for ( int  i = 1 ; i <= p ; i ++ )
        if ( S & ( 1 << ( i - 1 ) ) )
            cnt [ dot [ i ] . clr ] ++ ;
    for ( int  i = 1 ; i <= 10 ; i ++ )
        if ( cnt [ i ]  &&  cnt [ i ]  !=  sum [ i ] )
            return  0 ;
    return  1 ;
}
 
int  main ( ) {
    
    scanf ( "%d%d%d" , & n , & m , & p ) ;
    memset ( dp , 127 / 3 , sizeof ( dp ) ) ;
    inf = dp [ 0 ] [ 0 ] ;
    S = ( 1 << p ) - 1 ;
    for ( int  i = 1 ; i <= m ; i ++ ) {
        scanf ( "%d%d%d" , & x , & y , & w ) ;
        create ( x , y , w ) ;
        create ( y , x , w ) ;
    }
    for ( int  i = 1 ; i <= p ; i ++ ) {
        scanf ( "%d%d" , & dot [ i ] . clr , & dot [ i ] . id ) ;
        sum [ dot [ i ] . clr ] ++ ;
    }
    for ( int  i = 1 ; i <= p ; i ++ )
        dp [ dot [ i ] . id ] [ 1 << ( i - 1 ) ] = 0 ; 
    for ( int  s = 0 ; s <= S ; s ++ ) {
    	for ( int  i = 1 ; i <= n ; i ++ )
        	for ( int  s1 = s ; s1 ; s1 = ( s1 - 1 ) & s )
                dp [ i ] [ s ] = minx ( dp [ i ] [ s ] , dp [ i ] [ s1 ] + dp [ i ] [ s ^ s1 ] ) ;
        spfa ( s ) ;
    }
    memset ( ans , 127 / 3 , sizeof ( ans ) ) ;
    for ( int  s = 0 ; s <= S ; s ++ )
        for ( int  i = 1 ; i <= n ; i ++ )
            ans [ s ] = minx ( ans [ s ] , dp [ i ] [ s ] ) ;
    for ( int  s1 = 0 ; s1 <= S ; s1 ++ )
        if ( check ( s1 ) )
            ck [ s1 ] = true ;
    for ( int  s = 0 ; s <= S ; s ++ )
        if ( ck [ s ] ) 
            for ( int  s1 = s ; s1 ; s1 = ( s1 - 1 ) & s )
                if ( ck [ s1 ] )
                    ans [ s ] = minx ( ans [ s ] , ans [ s1 ] + ans [ s ^ s1 ] ) ;
    printf ( "%d" , ans [ S ] ) ;
    return  0 ;
}


要开O2,因为STL队列很慢。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值