题目描述:
魔术师的桌子上有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 ;
}