BZOJ 3714 HYBSZ 3714 巧妙的最小生成树

题目描述:

魔术师的桌子上有n个杯子排成一行,编号为1,2,…,n,其中某些杯子底下藏有一个小球,如果你准确地猜出是哪些杯子,你就可以获得奖品。花费c_ij元,魔术师就会告诉你杯子i,i+1,…,j底下藏有球的总数的奇偶性。

采取最优的询问策略,你至少需要花费多少元,才能保证猜出哪些杯子底下藏着球?

Input

第一行一个整数n(1<=n<=2000)。

第i+1行(1<=i<=n)有n+1-i个整数,表示每一种询问所需的花费。其中c_ij(对区间[i,j]进行询问的费用,1<=i<=j<=n,1<=c_ij<=10^9)为第i+1行第j+1-i个数。

Output

输出一个整数,表示最少花费。

Sample Input

5

1 2 3 4 5

4 3 2 1

3 4 5

2 1

5

Sample Output

7

Hint

看到最少花费,很容易想到最短路和最小生成树,然后根据题意,可以知道花费是累计的,可能是最小生成树。

如果我们知道 [ l , r ] 的奇偶性,又知道 [ r+1 , h ] 的奇偶性,我们就可以知道 [ l , h ] 的奇偶性 ; 

如果我们知道 [ l , r ] 的奇偶性,又知道 [ l , m ] 的奇偶性,我们就可以知道 [ m , r ] 的奇偶性 ;

这就类似于三个点连通,只要两条边连接,就可以互相推导。

累计整张图,就是最小生成树。

而且,可以认为有 n+1 个点,一般自己到自己是没有距离的,在题目中,  i j 坐标相等,仍然有费用,这样我们可以假设有 n+1 个点,比一般的 n 多出来的那个 1 是可以有距离的自己。查询 [ 1 , 1 ] 奇偶性是可以的。

Kruskal 并查集     时间复杂度 O(eloge) , 11208ms , 内存 24.7M

#include <algorithm>
#include <iostream>
#include <cstdio>
using namespace std ;
typedef long long LL ;
int n , i , j , top , cost ;
int father[2050] ;
struct Node{
	int u , v , w ;
	void Init( int i , int j , int cost ){
		u = i , v = j , w = cost ;
	}
} Edge[2000050] ;
bool cmp( const Node &a , const Node &b ){
	return a.w < b.w ;
}

int find( int u ){
	return u == father[u] ? u : father[u] = find( father[u] ) ;  // 并查集
}

int main(){
	LL ans = 0 ;
	scanf( "%d" , &n ) ;
	for( i = 0 ; i <= n ; ++i )
		father[i] = i ;
	for( i = 1 ; i <= n ; ++i )
		for( j = i+1 ; j <= n+1 ; ++j )
			scanf( "%d" , &cost ) , Edge[++top].Init( i , j , cost ) ;   // 统计边
	sort( Edge+1 , Edge+top+1 , cmp ) ;
	int cnt = 0 , one , two ;
	for( i = 1 ; i <= top ; ++i ){
		one = find( Edge[i].u ) ;
		two = find( Edge[i].v ) ;
		if( one != two ){
			father[two] = one ;
			ans += Edge[i].w ;
			if( ++cnt == n ) break ;        // 注意是 n+1 个点之间的最小生成树
		}
	}
	printf( "%lld\n" , ans ) ;
	return 0 ;
}

因为只有 2000 个点,但是边却是最大可以到 20000000 ,所以 Kruskal 在这里效率不高,Kruskal 适合于边少点多的稀疏图,适合维护。

prim 算法适合于边多点少的稠密图,因为 Prim 是把点加入集合,而 Kruskal 是把边加入集合,在这道题 边 >> 点,虽然 Prim 复杂度是O(N*N) , 但是,效率应该比 Kruskal 快

Prim 邻接矩阵 时间复杂度 O(n*n) , 6500ms , 内存 17 MB

#include <algorithm>
#include <iostream>
#include <cstdio>
using namespace std ;
#define INF 1000000001 
typedef long long LL ;
int n , i , j , cost ;
int Gragh[2005][2005] ;
int dis[2005] , book[2005] ;

void Prim(){
	for( i = 1 ; i <= n+1 ; ++i )   // 在这里 Gragh 不用初始化,因为Gragh[1][i]都是有边的,看矩阵第一行
		dis[i] = Gragh[1][i] ;
	book[1] = 1 ;
	int u , Min ;
	LL ans = 0 ;
	for( i = 1 ; i <= n ; ++i ){
		Min = INF ;
		for( j = 1 ; j <= n+1 ; ++j )          // 注意是 n+1 个点
			if( !book[j] && dis[j] < Min )
				Min = dis[j] , u = j ;
		book[u] = 1 ;
		ans += Min ; 
		for( j = 1 ; j <= n+1 ; ++j )
			if( !book[j] && dis[j] > Gragh[u][j] )
				dis[j] = Gragh[u][j] ;
	}
	printf( "%lld\n" , ans ) ;
}

int main(){
	scanf( "%d" , &n ) ;
	for( i = 1 ; i <= n ; ++i )
		for( j = i+1 ; j <= n+1 ; ++j )            // 加上自己有 n+1 个点
			scanf( "%d" , &cost ) , Gragh[i][j] = Gragh[j][i] = cost ;   // 双向图
	Prim() ;
	return 0 ;
}

从上面可以看出,稠密图 Prim 复杂度低于 Kruskal 

图中的点不多,如果加上堆优化,效果可能不大。 6664 ms , 差别很小,如果点多一些,在选择最小值方面会更快。

#include <algorithm>
#include <iostream>
#include <cstdio>
#include <queue>
using namespace std ;
#define INF 1000000001 
typedef long long LL ;
int n , i , j , cost , u ;
int Gragh[2005][2005] ;
int dis[2005] , book[2005] ;
struct Pair{
	int dis , u ;
	Pair( int _dis , int _u ){ dis = _dis , u = _u ; }
	bool operator < ( const Pair &b ) const {
		return dis > b.dis ;
	}
} ;

void Prim(){
	LL ans = 0 ;
	priority_queue<Pair> One ;
	for( i = 2 ; i <= n+1 ; ++i )
		One.push( Pair( dis[i] = Gragh[1][i] , i ) ) ;
	book[1] = 1 ;
	while( !One.empty() ){
		Pair top = One.top() ;
		One.pop() ;
		u = top.u ;
		if( dis[u] < top.dis ) 
			continue ;
		ans += top.dis ;
		for( i = 2 ; i <= n+1 ; ++i ){
			if( dis[i] > Gragh[u][i] )
				One.push( Pair( dis[i] = Gragh[u][i] , i ) ) ;
		}
	}
	printf( "%lld\n" , ans ) ;
}

int main(){
	scanf( "%d" , &n ) ; 
	for( i = 1 ; i <= n ; ++i )
		for( j = i+1 ; j <= n+1 ; ++j )
			scanf( "%d" , &cost ) , Gragh[i][j] = Gragh[j][i] = cost ;
	Prim() ;
	return 0 ;
}






评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值