[JZOJ]3413.KC的瓷器

mac2024-10-19  58

Description

KC来到了一个盛产瓷器的国度。他来到了一位商人的店铺。在这个店铺中,KC看到了一个有n(1<=n<=100)排的柜子,每排都有一些瓷器,每排不超过100个。那些精美的艺术品使KC一下心动了,决定从N排的商品中买下m(1<=m<=10000)个瓷器。 这个商人看KC的脸上长满了痘子,就像苔藓一样,跟精美的瓷器相比相差太多,认为这么精致的艺术品被这样的人买走艺术价值会大打折扣。商人感到不爽,于是规定每次取商品只能取其中一排的最左边或者最右边那个,想为难KC。 现在KC又获知每个瓷器的价值(用一个不超过100的正整数表示),他希望取出的m个商品的总价值最大。

Input

输入文件的第一行包括两个正整数n,m; 接下来2到n+1行,第i行第一个数表示第i排柜子的商品数量Si,接下来Si个数表示从左到右每个商品的价值。

Output

输出文件只有一个正整数,即m个商品最大的总价值。

Sample Input

输入1: 2 3 3 3 7 2 3 4 1 5 输入2: 1 3 4 4 3 1 2

Sample Output

输出1: 15 样例解释1: 取第一排的最左边两个和第二排的最右边那个。总价直为3+7+5=15; 输出2: 9

Data Constraint

对于10%的数据, S i = 1 , 1 < = i < = n S_i=1,1<=i<=n Si=1,1<=i<=n。 对于另外10%的数据, n = 1 n=1 n=1.

Analysis

n n n行, n ∈ [ 1 , 100 ] n\in[1,100] n[1,100],每行个数有可能不同。一共购买 m m m个物品, m ∈ [ 1 , 10000 ] m\in[1,10000] m[1,10000]每行可以购买前 i i i个物品和后 j j j个物品, i , j ∈ [ 0 , 100 ] i,j \in [0,100] i,j[0,100]

Solution

容易发现,每行的最优都可以单独预处理算出,而当每行购买 i i i个商品的最大价值确定后,就可以转换为一个多重背包问题。 于是预处理每一行的购买 i i i件商品的最大价值。

设 在 该 行 一 共 有 s 个 商 品 , 购 买 k 个 商 品 , 在 左 边 购 买 m 个 商 品 , 则 在 右 边 购 买 k − m 个 商 品 设在该行一共有s个商品,购买k个商品,在左边购买m个商品,则在右边购买k-m个商品 skmkm 容易得到: v a l [ k ] = ∑ m = 0 k ( ∑ i = 1 m v a l i + ∑ j = s − ( k − m ) + 1 s v a l j ) val[k] = \sum_{m=0}^k (\sum_{i=1}^m val_i +\sum_{j=s-(k-m)+1}^s val_j) val[k]=m=0k(i=1mvali+j=s(km)+1svalj)

当然,区间和我们可以用简单的前缀和算出:

for(int i = 1;i<=s;++i)//处理前缀和为以后求区间和做准备 sum[i] = sum[i-1] + val[i];

于是上式可以简化成:

设 s 个 商 品 在 左 边 取 了 m 个 , 在 右 边 取 了 k − m 个 设s个商品在左边取了m个,在右边取了k-m个 smkm v a l [ k ] = s u m m + s u m s − s u m s − ( k − m ) val[k] = sum_m + sum_s - sum_{s-(k-m)} val[k]=summ+sumssums(km) 前缀和求区间和: s u m [ i , j ] = s u m j − s u m i − 1 sum_{[i,j]}=sum_j-sum_{i-1} sum[i,j]=sumjsumi1

由于数据量小,可以直接枚举这个m,得到此时的最大价值。 那么我们就可以用O(s)的复杂度预处理,随后用O( s 2 s^2 s2)的复杂度求出该商品购买所有件数时的最大价值。 其中 s ∈ [ 1 , 100 ] s\in[1,100] s[1,100],有 n n n种商品, n ∈ [ 1 , 100 ] n\in[1,100] n[1,100],每种都需要 O ( s 2 ) O(s^2) O(s2)进行预处理,复杂度可能达到 O ( n s 2 ) O(ns^2) O(ns2)但由于常数很小,还是可以接受的。

for(register int k = 1;k<=n;++k)//第k种 for(register int i = 1;i<=s[k][0];++i) //表示取i个的时候,s[k][0]代表该商品总个数 for(register int j = 0;j<=i;++j) dp[k][i] = MAX(dp[k][i],\ sum[k][j] + sum[k][s[k][0]] - sum[k][s[k][0] - i + j]);

接下来就是多重背包问题了: 有 n 种 商 品 , 每 种 商 品 有 p i 个 , 第 i 种 商 品 取 j 个 得 到 的 最 大 价 值 为 s i j , 有n种商品,每种商品有p_i个,第i种商品取j个得到的最大价值为s_{ij}, npiijsij 每 个 商 品 的 体 积 都 为 1 , 容 量 为 m , 问 最 多 能 取 得 多 大 价 值 。 每个商品的体积都为1,容量为m,问最多能取得多大价值。 1m

//dp[i][j]代表在第i行取j个商品的最大获利 //f[i][j]代表在前i行取j个商品 for(register int i = 1;i<=n;++i) //在第i行选商品 for(register int j = 1;j<=m;++j)//此时一共选择j个商品 for(register int k = 0;k<=s[i][0] && k <= j;++k) { //k是在这一行取得多少商品,注意k不能超过当前取商品量 //当前在前i行取j个商品,在当前行取k个商品,那么就在前i-1行取j-k个商品 dp[i][j] = MAX(dp[i][j],dp[i-1][j-k] + f[i][k]); }

本题结束。

Code

#include <cstdio> #define MAX(x,y) ((x)>(y)?(x):(y)) using namespace std; void read(int &r) { static char c; for(c=getchar();c>'9'||c<'0';c=getchar()); for(;c<='9'&&c>='0';r=(r<<1)+(r<<3)+c-48,c=getchar()); } int n,m; int s[105][105]; int dp[105][10005];//表示从第i排取出j个的最大价值和 int f[105][105]; int sum[105][105]; int main() { read(n); read(m); for(register int i = 1;i<=n;++i) { read(s[i][0]); for(register int j = 1;j<=s[i][0];++j) read(s[i][j]); } for(register int i = 1;i<=n;++i)//处理前缀和为以后求区间和做准备 for(register int j = 1;j<=s[i][0];++j) sum[i][j] = sum[i][j-1] + s[i][j]; //每行按行dp算出取n个商品时的最大值 for(register int k = 1;k<=n;++k) for(register int i = 1;i<=s[k][0];++i) //表示取i个的时候 for(register int j = 0;j<=i;++j) f[k][i] = MAX(f[k][i],sum[k][j] + sum[k][s[k][0]] - sum[k][s[k][0] - i + j]); //在左边取j个,右边取 i - j个 //使用前缀和的方法求区间 //f[i][j]代表在第i行取j个商品的最大获利 //dp[i][j]代表在前i行取j个商品 for(register int i = 1;i<=n;++i) //在第i行选商品 for(register int j = 1;j<=m;++j)//此时一共选择j个商品 for(register int k = 0;k<=s[i][0] && k <= j;++k) { //k是在这一行取得多少商品,注意k不能超过当前取商品量 //当前在前i行取j个商品,在当前行取k个商品,那么就在前i-1行取j-k个商品 dp[i][j] = MAX(dp[i][j],dp[i-1][j-k] + f[i][k]); } printf("%d",dp[n][m]); return 0; }
最新回复(0)