题目大意
题目描述
Gagamboy 需要购买 r r r 种不同的化学物质,每种需要 1kg。电商平台上共有 c c c 个卖家。
给定一个 r × c r \times c r × c 的价格矩阵 A A A ,其中 a i , j a_{i,j} a i , j 表示从卖家 j j j 处购买 1kg 化学物质 i i i 的价格。
此外,对于每个卖家 j j j ,只要从该卖家处购买了任何数量的化学物质(哪怕只有一种),都需要额外支付一笔固定的配送费 d j d_j d j 。
请计算购买到所有 r r r 种化学物质(每种至少 1kg)所需的最小总花费 。本题包含多组独立测试数据。
输入格式
第一行包含一个整数 T T T (1 ≤ T ≤ 10 1 \le T \le 10 1 ≤ T ≤ 1 0 ),表示测试数据的组数。
对于每组测试数据:
第一行包含两个由空格分隔的整数 r r r 和 c c c (1 ≤ r , c 1 \le r, c 1 ≤ r , c ,且 r × c ≤ 250 r \times c \le 250 r × c ≤ 2 5 0 )。
接下来 r r r 行,每行包含 c c c 个由空格分隔的整数,第 i i i 行的第 j j j 个数为 a i , j a_{i,j} a i , j (1 ≤ a i , j ≤ 1 0 15 1 \le a_{i,j} \le 10^{15} 1 ≤ a i , j ≤ 1 0 1 5 )。
最后一行包含 c c c 个由空格分隔的整数 d 1 , d 2 , … , d c d_1, d_2, \dots, d_c d 1 , d 2 , … , d c (1 ≤ d j ≤ 1 0 15 1 \le d_j \le 10^{15} 1 ≤ d j ≤ 1 0 1 5 ),表示每个卖家的配送费。
输出格式
对于每组测试数据,输出一行一个整数,表示完成购买所需的最小总花费。
样例输入
1 2 3 4 5 6 7 8 9 10 11 12 2 3 5 1 3 5 7 9 5 7 9 1 3 9 1 3 5 7 4 3 2 3 4 4 3 1 2 4 2 3 1 4 1 2 3 2 1 2 4 4
样例输出
样例解释
对于第一组样例:
一种最优的购买方案如下:
向卖家 2 购买化学物质 1 和 3。它们的价格分别为 a 1 , 2 = 3 a_{1,2} = 3 a 1 , 2 = 3 和 a 3 , 2 = 1 a_{3,2} = 1 a 3 , 2 = 1 。支付这笔订单所需的配送费为 d 2 = 3 d_2 = 3 d 2 = 3 。
向卖家 4 购买化学物质 2。它的价格为 a 2 , 4 = 1 a_{2,4} = 1 a 2 , 4 = 1 。支付这笔订单所需的配送费为 d 4 = 3 d_4 = 3 d 4 = 3 。
总花费为:( ( 3 + 1 ) + 3 ) + ( 1 + 3 ) = 11 ((3 + 1) + 3) + (1 + 3) = 11 ( ( 3 + 1 ) + 3 ) + ( 1 + 3 ) = 1 1 。
对于第二组样例:
最优方案能达到的最小花费同样为 11。
思路讲解
这道题目的做法,首先,需要想到:绷不住了,这道题目,观察到 chem * sell <= 250,那么,chem,sell 理论上来说,应该不会同时超过 16,因为 16*16=256,这样子,chem 小,就对 chem 进行状态压缩 ,sell 小,就对 sell 进行状态压缩 。
确实是感觉到,如果不进行状态压缩,那么这道题目非常难做 。
如果进行状态压缩的话,状态压缩 seller 非常好做。枚举的是给哪些 seller 付了这个运费。
给哪些 seller 付了这个运费,就只能从这些 seller 这里购买化学品。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 ll ans=INF; for (int s=1 ;s<(1 <<M_j_sell);++s) { vector<ll> mn_price (N_i_chem+2 ,INF) ; ll offset_j=1 ; ll price=0 ; for (int j=0 ;j<M_j_sell;++j) { if (s>>j&1 ) { price+=D_j_sell[j+offset_j]; for (int i=1 ;i<=N_i_chem;++i) { mn_price[i]=min (mn_price[i],A[i][offset_j+j]); } } } for (int i=1 ;i<=N_i_chem;++i) { price+=mn_price[i]; } ans=min (ans,price); } cout<<ans<<"\n" ;
我们来看,状态压缩 chem ,我们怎么做?这个会难一点。
我们不难观察到,一个子集 S 的最优解,一定是这样的:把一部分子集交给一个买家,把另一部分子集交给一个买家,把又一部分子集交给又一买家 ……。
在状态压缩 化学品的时候,我们需要学习一种写法,即枚举 mask 的子集。
1 2 3 4 5 6 for (int S = 0 ; S < (1 << r); S++) { for (int T = S; T > 0 ; T = (T - 1 ) & S) { dp[S] = min (dp[S], dp[S ^ T] + best[T]); } }
枚举 mask 的所有子集,是 O ( 3 n ) O(3^n) O ( 3 n ) 。
如果说枚举 seller,允许你去这个枚举子集,是不是会简单一点?
预处理 :对每个非空子集 T T T ,预先算好从所有卖家中买 T T T 的最小花费:best(T)的意思就是在包括这个运费的情况下,该 chem 子集 T,从哪一个买家那里采购,所需花费最低 ?
best ( T ) = min j = 1 c ( d j + ∑ i ∈ T a i , j ) \text{best}(T) = \min_{j=1}^{c} \left(d_j + \sum_{i \in T} a_{i,j}\right)
best ( T ) = j = 1 min c ( d j + i ∈ T ∑ a i , j )
具体而言就是:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 ll offset_i=1 ; vector<ll> best ((1 <<N_i_chem)+2 ) ;for (int s=1 ;s<(1 <<N_i_chem);++s) { ll lans=INF; for (int j=1 ;j<=M_j_sell;++j) { ll price=D_j_sell[j]; for (int i=0 ;i<N_i_chem;++i) { if (s>>i&1 ) { price+=A[offset_i+i][j]; } } lans=min (lans,price); } best[s]=lans; }
转移方程如下,容易写出:
d p [ S ] = min ∅ ≠ T ⊆ S ( d p [ S ∖ T ] + best ( T ) ) dp[S] = \min_{\emptyset \neq T \subseteq S} \left(dp[S \setminus T] + \text{best}(T)\right)
d p [ S ] = ∅ = T ⊆ S min ( d p [ S ∖ T ] + best ( T ) )
你可能会有疑问,这个运费会不会重复算 ?答案是是的,但是我们肯定不会采用运费重复算的组合 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 vector<ll> dp ((1 <<N_i_chem)+2 ) ;ll offset_i=1 ; vector<ll> best ((1 <<N_i_chem)+2 ) ;for (int s=1 ;s<(1 <<N_i_chem);++s) { ll lans=INF; for (int j=1 ;j<=M_j_sell;++j) { ll price=D_j_sell[j]; for (int i=0 ;i<N_i_chem;++i) { if (s>>i&1 ) { price+=A[offset_i+i][j]; } } lans=min (lans,price); } best[s]=lans; } for (int s=1 ;s<(1 <<N_i_chem);++s) { ll lans=INF; for (int t=s;t>0 ;t=(t-1 )&s) { lans=min (lans,dp[s^t]+best[t]); } dp[s]=lans; } cout<<dp[(1 <<N_i_chem)-1 ]<<"\n" ;
AC代码
AC
https://codeforces.com/gym/106262/submission/365538444
源代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 #include <bits/stdc++.h> #define all(vec) vec.begin(),vec.end() #define lson(o) (o<<1) #define rson(o) (o<<1|1) #define SZ(a) ((long long) a.size()) #define debug(var) cerr << #var <<" = [" <<var<<"]" <<"\n" ; #define debug1d(a) \ cerr << #a << " = [" ; \ for (int i = 0; i < (int)(a).size(); i++) \ cerr << (i ? ", " : "" ) << a[i]; \ cerr << "]\n" ; #define debug2d(a) \ cerr << #a << " = [\n" ; \ for (int i = 0; i < (int)(a).size(); i++) \ { \ cerr << " [" ; \ for (int j = 0; j < (int)(a[i]).size(); j++) \ cerr << (j ? ", " : "" ) << a[i][j]; \ cerr << "]\n" ; \ } \ cerr << "]\n" ; #define cend cerr<<"\n-----------\n" #define fsp(x) fixed<<setprecision(x) using namespace std;using ll = long long ;using ull = unsigned long long ;using DB = double ;using i128 = __int128;using CD = complex<double >;static constexpr ll MAXN = (ll)1e6 +10 , INF = (1ll <<61 )-1 ;static constexpr ll mod = 998244353 ; static constexpr double eps = 1e-8 ;const double pi = acos (-1.0 );ll lT,testcase; void Solve () { ll N_i_chem,M_j_sell; cin >> N_i_chem >> M_j_sell; vector<vector<ll>> A (N_i_chem+2 ,vector <ll>(M_j_sell+2 )); for (int i=1 ;i<=N_i_chem;++i) { for (int j=1 ;j<=M_j_sell;++j) { cin>>A[i][j]; } } vector<ll> D_j_sell (M_j_sell+2 ) ; for (int j=1 ;j<=M_j_sell;++j) { cin>>D_j_sell[j]; } if (N_i_chem<=M_j_sell) { vector<ll> dp ((1 <<N_i_chem)+2 ) ; ll offset_i=1 ; vector<ll> best ((1 <<N_i_chem)+2 ) ; for (int s=1 ;s<(1 <<N_i_chem);++s) { ll lans=INF; for (int j=1 ;j<=M_j_sell;++j) { ll price=D_j_sell[j]; for (int i=0 ;i<N_i_chem;++i) { if (s>>i&1 ) { price+=A[offset_i+i][j]; } } lans=min (lans,price); } best[s]=lans; } for (int s=1 ;s<(1 <<N_i_chem);++s) { ll lans=INF; for (int t=s;t>0 ;t=(t-1 )&s) { lans=min (lans,dp[s^t]+best[t]); } dp[s]=lans; } cout<<dp[(1 <<N_i_chem)-1 ]<<"\n" ; }else { ll ans=INF; for (int s=1 ;s<(1 <<M_j_sell);++s) { vector<ll> mn_price (N_i_chem+2 ,INF) ; ll offset_j=1 ; ll price=0 ; for (int j=0 ;j<M_j_sell;++j) { if (s>>j&1 ) { price+=D_j_sell[j+offset_j]; for (int i=1 ;i<=N_i_chem;++i) { mn_price[i]=min (mn_price[i],A[i][offset_j+j]); } } } for (int i=1 ;i<=N_i_chem;++i) { price+=mn_price[i]; } ans=min (ans,price); } cout<<ans<<"\n" ; } } signed main () { ios::sync_with_stdio (false ); cin.tie (nullptr ); cout.tie (nullptr ); #ifdef LOCAL cout.setf (ios::unitbuf); #endif cin >> lT; for (testcase=1 ;testcase<=lT;++testcase) Solve (); return 0 ; }
心路历程(WA,TLE,MLE……)