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

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

我的:https://blog.csdn.net/jerry_wang119/article/details/80001711
我一开始学习的:https://blog.csdn.net/wu_tongtong/article/details/78992913

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

然而学习了之后也没有什么卵用,发现并不只是斯坦纳树这么简单呐!
 
 
题解:
 
 
由于存在相同频率之间的连通,所以和一般的斯坦纳树是不同的(发现斯坦纳树所指定的结点频率是相同的),需要二次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 ;
}
Ans for this

 

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

posted @ 2018-06-07 23:47  horsepower2001  阅读(155)  评论(0编辑  收藏  举报