题解——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 ;
}