P2792 [JSOI2008]小店购物
给出一个易于理解且好写的做法。
有一个贪心做法,
-
从源点向每一种商品连 \(c_i \times m_i\) 的边。
-
对于优惠方案 \((A,B,P)\) , 从 \(A\) 向 \(B\) 连 \(P \times m_B\) 的边。
最后源点的最小树形图即为答案?
这样做显然有问题,因为如果同时存在优惠方案 \((A,B)\) 和 \((B,A)\) ,那么先买一件 \(A\) , 再用优惠价买完 \(B\) , 然后用优惠价买完 \(A\),这种方案可能更优。
所以可以将一件商品拆成 \(2\) 个点,表示买 \(1\) 件和买 \(m_i-1\) 件,连边方式同上。
注意细节,不要多建新点。
#include <cstdio>
#include <cstring>
#define Inf 1e9
const int MAXN = 100 , MAXM = MAXN * ( MAXN + 1 );
int m;
struct Edge{ int u , v; double w; Edge(){} Edge( int U , int V , double W ) { u = U , v = V , w = W; } } Graph[ MAXM + 5 ];
void Add_Edge( int u , int v , double w ) { Graph[ ++ m ] = Edge( u , v , w ); }
int n , k , rt , num[ MAXN + 5 ];
struct node { int v; double w; node(){} node( int V , double W ) { v = V , w = W; } } pre[ MAXN + 5 ];
int vis[ MAXN + 5 ] , bel[ MAXN + 5 ];
double Zhuliu( int rt ) {
double Ans = 0;
while( 1 ) {
for( int i = 1 ; i <= n ; i ++ ) pre[ i ] = node( 0 , Inf );
for( int i = 1 ; i <= m ; i ++ ) {
int u = Graph[ i ].u , v = Graph[ i ].v; double w = Graph[ i ].w;
if( u != v && pre[ v ].w > w ) pre[ v ] = node( u , w );
}
for( int i = 1 ; i <= n ; i ++ ) if( i != rt && !pre[ i ].v ) return -1;
int cnt = 0;
memset( vis , 0 , sizeof( vis ) );
memset( bel , 0 , sizeof( bel ) );
for( int i = 1 , u ; i <= n ; i ++ ) {
if( i == rt ) continue; Ans += pre[ i ].w;
for( u = i ; u != rt && vis[ u ] != i ; vis[ u ] = i , u = pre[ u ].v );
if( u != rt && !bel[ u ] ) {
bel[ u ] = ++ cnt;
for( ; u != rt && !bel[ pre[ u ].v ] ; u = pre[ u ].v , bel[ u ] = cnt );
}
}
if( !cnt ) break;
for( int i = 1 ; i <= n ; i ++ ) if( !bel[ i ] ) bel[ i ] = ++ cnt;
for( int i = 1 ; i <= m ; i ++ ) {
int u = Graph[ i ].u , v = Graph[ i ].v; double w = Graph[ i ].w;
Graph[ i ].u = bel[ u ] , Graph[ i ].v = bel[ v ];
if( bel[ u ] != bel[ v ] ) Graph[ i ].w = w - pre[ v ].w;
}
n = cnt; rt = bel[ rt ];
}
return Ans;
}
int cnt , id[ MAXN + 5 ][ 2 ];
double c;
int main( ) {
scanf("%d",&n); rt = ++ cnt;
for( int i = 1 ; i <= n ; i ++ ) {
scanf("%lf %d",&c,&num[ i ]);
if( num[ i ] > 0 ) id[ i ][ 0 ] = ++ cnt , Add_Edge( rt , id[ i ][ 0 ] , c );
if( num[ i ] > 1 ) id[ i ][ 1 ] = ++ cnt , Add_Edge( rt , id[ i ][ 1 ] , c * ( num[ i ] - 1 ) );
}
scanf("%d",&k);
for( int i = 1 , u , v ; i <= k ; i ++ ) {
scanf("%d %d %lf",&u,&v,&c);
if( num[ u ] > 0 && num[ v ] > 0 ) Add_Edge( id[ u ][ 0 ] , id[ v ][ 0 ] , c );
if( num[ u ] > 0 && num[ v ] > 1 ) Add_Edge( id[ u ][ 0 ] , id[ v ][ 1 ] , c * ( num[ v ] - 1 ) );
if( num[ u ] > 1 && num[ v ] > 0 ) Add_Edge( id[ u ][ 1 ] , id[ v ][ 0 ] , c );
if( num[ u ] > 1 && num[ v ] > 1 ) Add_Edge( id[ u ][ 1 ] , id[ v ][ 1 ] , c * ( num[ v ] - 1 ) );
}
n = cnt;
printf("%.2f\n", Zhuliu( rt ) );
return 0;
}