zoukankan      html  css  js  c++  java
  • 题解——Watering Hole(最小生成树+虚拟节点)

    题解——Watering Hole(最小生成树+虚拟节点)

    今天考试T1,这么水,我居然写了一个更加复杂的生成树


    题面魔改版

    Description
    学园都市迎来了经济萧条期,火力发电厂接连倒闭.瞅准了商机,商人BerryKanry向操控电磁的能力者——御坂美琴发出了合作邀请,准备开一家电力公司.
    他包揽了学园都市N个区域的供电项目.一个区域如果想得到电力,要么在这个区域建立一个超级电源,要么和已经获得电力的某城市建立电力纽带.他的合作意图是让御坂美琴提供电力,而他负责策划具体电路设计.御坂美琴开出的合作条件是这样的:
    对于区域i(1 <= i <= N),在此区域创造一个超级电源的费用是wi,在两个区域i和j之间建立电力纽带的费用是pij.
    BerryKanry想知道,如何设计一种方案使N个区域都得到供电的同时,自己向御坂美琴支付的合作费用最少呢?
    Input
    第一行:一个数n
    第二行到第n+1行:第i+1行含有一个数wi
    第n+2行到第2n+1行:第n+1+i行有n个被空格分开的数,第j个数代表pij。
    Output
    第一行:一个单独的数代表最小合作费用.
    Sample Input
    4
    5
    4
    4
    3
    0 2 2 2
    2 0 3 3
    2 3 0 4
    2 3 4 0
    Sample Output
    9
    Sample Explanation
    BerryKanry在区域4上建立超级电源,然后其他的区域都与区域4建立电力纽带,这样就要花费3+2+2+2=9.

    思路

    由于带了点权,导致点权和边权的矛盾,并没有办法跑出一个合法的最小生成树。

    那么

    把点权下放给虚拟的边做边权不就可以了吗?

    建立一个虚拟汇点,和所有电源连边,然后,没有然后,保证连通就可以了,跑一个最小生成树。(正确性显然)

    标准code:

    #include<bits/stdc++.h>
    using namespace std ; 
    const  int  MAXN = 305 ;
    inline int read(){
    	int s= 0 ; char g = getchar() ;  while( g>'9'||g<'0')g=getchar() ; 
    	while(g>='0'&&g<='9')s=s*10+g-'0',g=getchar() ;
    	return s ;
    } 
    struct ap{
    	int  to , from , val ;
    }t[ MAXN*MAXN ] , p[ MAXN+10 ];
    int  w[ MAXN ] ,  a[ MAXN ][ MAXN ] , fa[ MAXN ] , wk[ MAXN ] ;
    int  N , M , tot = 0 , ans = 0 , cnt = 0 ;
    int  to[ MAXN*2 ] , nex[ MAXN*2 ] , head[ MAXN ] , toot = 0 ; 
    bool cmp( ap x , ap y ){
    	return x.val < y.val ; 
    }
    void  add( int  x , int  y ){
    	to[ ++toot ] = y , nex[ toot ] = head[ x ] , head[ x ] = toot ;
    }
    int  find( int x ){
    	if( x != fa[ x ] ) return fa[ x ] = find( fa[ x ] ) ;
    	else return x ;
    }
    int main(){
    	//freopen("Berry.in","r",stdin);
    	//freopen("Berry.out","w",stdout);
    	N = read() ; int  tol ;
    	for( int i = 1 ; i <= N ; ++i ){
    		w[ i ] = read() , fa[ i ] = i ;
    		if( ans > w[ i ] )ans = w[ i ] , tol = i ;
    	}
    	for( int i = 1 ; i <= N ; ++i )
    	    for( int j = 1 ; j <= N ; ++j )
    	    	t[ ++tot ].from = i , t[ tot ].to = j , t[ tot ].val = read() ;
    	for( int i = 1 ; i <= N ; ++i )
    	    t[ ++tot ].from = N+1 , t[ tot ].to = i , t[ tot ].val = w[ i ] ;//虚拟节点
    	sort( t+1 , t+tot+1 , cmp ) ;
    	for( int i = 1 ; i <= tot ; ++i ){
    		int x = find( t[i].from ) , y = find( t[i].to ) ;
    		if( x == y )continue ;
    		fa[ x ] = y ;
    		ans += t[i].val ; 
    		cnt++ ;
    		if( cnt >= N )break ;
    	}
    	cout<<ans ; 
    }
    

    关于我的玄学做法

    我是在原图上建立最小生成树,然后有一个奇妙的性质,每次新建一个电源,都可以使最小生成树上至少减少一条边。(正确性已证)

    对于每两个电源,他们最短路径上就可以删掉一条边。

    对于初始的最小生成树,我们直接加入w[ ]最小的电源,然后按边权从大到小判断是加电源即可。这个部分分很妙。

    #include<bits/stdc++.h>
    using namespace std ; 
    const  int  MAXN = 305 ;
    inline int read(){
    	int s= 0 ; char g = getchar() ;  while( g>'9'||g<'0')g=getchar() ; 
    	while(g>='0'&&g<='9')s=s*10+g-'0',g=getchar() ;
    	return s ;
    } 
    struct ap{
    	int  to , from , val ;
    }t[ MAXN*MAXN ] , p[ MAXN+10 ];//但正确性,证明了90%plus,要是WA了,就怪那些剩下的。
    int  w[ MAXN ] ,  a[ MAXN ][ MAXN ] , fa[ MAXN ] , wk[ MAXN ] ;// 时间是卡着上线的,随时可能TLE 
    int  N , M , tot = 0 , cnt = 0 , ans = (1<<30) , wknum = 0 , dfsans , dfstol ;
    int  to[ MAXN*2 ] , nex[ MAXN*2 ] , head[ MAXN ] , toot = 0 , dfsfrom , dfsto ; 
    bool  used[ MAXN ] ; //生成树中的边 
    bool cmp( ap x , ap y ){
    	return x.val < y.val ; 
    }
    bool cmp2( ap x , ap y ){
    	return x.val > y.val ;
    }
    void  add( int  x , int  y ){
    	to[ ++toot ] = y , nex[ toot ] = head[ x ] , head[ x ] = toot ;
    }
    int  find( int x ){
    	if( x != fa[ x ] ) return fa[ x ] = find( fa[ x ] ) ;
    	else return x ;
    }
    void  dfs( int  u , int fa , bool che ){
    	if( che ){
    		if( dfsans > w[ u ] )dfsans = w[ u ] , dfstol = u ;
    	}
    	for( int i = head[ u ] ; i ; i = nex[ i ] ){
    		if( to[ i ] == fa )continue ;
    		if( che || ( dfsto == u && dfsfrom == to[ i ] ) || ( dfsfrom == u && dfsto == to[ i ]  ) )
    		    dfs( to[ i ] , u , 1 ) ;
    		else dfs( to[ i ] , u , 0 ) ;
    	}
    }
    int main(){
    	freopen("Berry.in","r",stdin);
    	freopen("Berry.out","w",stdout);
    	N = read() ; int  tol ;
    	for( int i = 1 ; i <= N ; ++i ){
    		w[ i ] = read() , fa[ i ] = i ;
    		if( ans > w[ i ] )ans = w[ i ] , tol = i ;
    	}
    	wk[ ++wknum ] = tol ;//首站,相同情况下正确性可证明 
    	for( int i = 1 ; i <= N ; ++i )
    	    for( int j = 1 ; j <= N ; ++j )
    	    	t[ ++tot ].from = i , t[ tot ].to = j , t[ tot ].val = read() ;
    	sort( t+1 , t+tot+1 , cmp ) ;
    	for( int i = 1 ; i <= tot ; ++i ){
    		int x = find( t[i].from ) , y = find( t[i].to ) ;
    		if( x == y )continue ;
    		fa[ x ] = y ;
    		ans += t[i].val ; 
    		p[ ++cnt ].from = t[i].from , p[cnt].to=t[i].to , p[cnt].val = t[i].val ;
    		add( t[i].from , t[i].to ) , add( t[i].to , t[i].from ) ;
    		if( cnt >= N-1 )break ;
    	}//克鲁斯卡尔 
    	sort( p+1 , p+cnt+1 , cmp2 ) ;
    	for( int i = 1 ; i <= cnt ; ++i ){
    		dfsfrom = p[i].from , dfsto = p[i].to ;dfsans = (1<<30) , dfstol = 0 ;
    		for( int k = 1 ; k <= wknum ; ++k )// dfsans , dfstol , dfsfrom , dfsto
    			 dfs( k , k , 0 ) ;
    		if( dfsans <= p[i].val ){//相同情况下优先建立 
    			ans = ans + dfsans - p[i].val ; //建立
    			wk[ ++wknum ] = dfstol , w[ dfstol ] = 0 ;//防止重复建厂 
    		}
    	}
    	//for( int i = 1 ; i <= cnt ; ++i )cout<<p[ i ].from <<" "<< p[i].to <<" "<<p[i].val<<endl  ;
    	cout<<ans ; 
    }
    

    如有不足,请大佬指出

  • 相关阅读:
    二叉树——数组的异或和
    二叉树——最长子数组累加和
    二叉树——平衡二叉搜索树 TreeSet, TreeMap
    二叉树——大楼的轮廓线
    数据结构--Morris遍历--二叉树的遍历
    数据结构--单调栈--烽火台
    数据结构--单调栈--求最大子矩阵的大小
    数据结构--单调栈--构造数组的MaxTree
    数据结构--单调栈结构
    字符串的压缩和解压缩
  • 原文地址:https://www.cnblogs.com/ssw02/p/11254843.html
Copyright © 2011-2022 走看看