( ext{Description})
( ext{Solution})
首先看这个数据范围和个数总和为 (k) 的限制都很像背包吧。
把每个数组拆成长度为 ([1,k]) 的前缀再做背包显然在时间和正确性上都不可取(不好控制每个数组只选一个)。
我们都陷入怪圈。
感觉很多这种题都有一个结论来限制时间。比如这题:数组是不降的。
有一个结论:最多只有一个数组选了但是没有选完。
考虑有 (i,j) 两个数组分别选到第 (p_i,p_j) 的数。如果 (a_{i,p_i}le a_{j,p_j}),又因为有 (a_{j,p_j}le a_{j,p_{j+1}}),那么舍 (p_i) 而取 (p_{j+1}) 也,而且新的数组肯定也有 (a_{i,p_i}le a_{j,p_j}),差距还会更大。那显然两个数组如果都选了没有选完,可以削弱其中一个,不停选另一个知道某个数组全部选或全选为止。
考虑枚举那个选了没选完的数组,将其他数组抽象成一件物品,可以分别进行背包,时间复杂度 (mathcal O(n^2 imes k))。
如何优化?每次选一个数组都跑其它数组其实有很多重复,我们可以进行分治来优化。时间复杂度 (mathcal O(n imes k imes log n))。
详见代码。
( ext{Code})
#include <cstdio>
#define rep(i,_l,_r) for(signed i=(_l),_end=(_r);i<=_end;++i)
#define print(x,y) write(x),putchar(y)
template <class T> inline T read(const T sample) {
T x=0; int f=1; char s;
while((s=getchar())>'9'||s<'0') if(s=='-') f=-1;
while(s>='0'&&s<='9') x=(x<<1)+(x<<3)+(s^48),s=getchar();
return x*f;
}
template <class T> inline void write(T x) {
if(x<0) return (void)(putchar('-'),write(-x));
if(x>9) write(x/10);
putchar(x%10^48);
}
#include <iostream>
using namespace std;
typedef long long ll;
const int maxn=3005;
int n,k,w[maxn][maxn];
ll val[maxn],ans,tmp,f[maxn];
void DP(int l,int r) {
if(l==r) { // 每个数组都会被选中成为选了但是没有选完的数组
tmp=0;
rep(i,1,w[l][0]) {
tmp+=w[l][i];
ans=max(ans,f[k-i]+tmp);
}
return;
}
int mid=l+r>>1; ll t[maxn];
rep(i,0,k) t[i]=f[i];
rep(i,l,mid) for(int j=k;j>=w[i][0];--j) f[j]=max(f[j],f[j-w[i][0]]+val[i]);
DP(mid+1,r); // 保证那个数组在 [mid+1,r],那么 [l,mid] 就可以全部抽象成意见物品背包,这样就不用选中 [mid+1,r] 的每个数都做一次 [l,mid] 的背包
rep(i,0,k) f[i]=t[i];
rep(i,mid+1,r) for(int j=k;j>=w[i][0];--j) f[j]=max(f[j],f[j-w[i][0]]+val[i]);
DP(l,mid);
}
int main() {
int y;
n=read(9),k=read(9);
rep(i,1,n) {
w[i][0]=read(9);
rep(j,1,w[i][0]) {
y=read(9);
if(j<=k) w[i][j]=y,val[i]+=w[i][j];
}
w[i][0]=min(w[i][0],k);
}
DP(1,n);
print(ans,'
');
return 0;
}