前言
做过原题的和没做过的都切了,只有我拉了大胯。
题目
讲解
这道题我在一开始方向就错了,我以为是什么奇技淫巧优化 DP,可以把 (O(nk^2)) 优化成 (O(nk)) 或者 (O(nklog_2k))。
先丢一个结论:
- 至多存在一个数组没取满。
证明的话假设有两个没取满,大的那个增多,小的那个减少,显然不劣。典型易证难想结论。
接下来考虑枚举没取满的那个数组,我们希望得到其它数组的背包结果。
前后缀拼起来并没有什么用,但是这玩意竟然可以分治!是个人均都会的trick,被开除人籍了
大概就是你先记录一下当前背包状态,把前半部分背包一下,后半部分丢进去处理。
出来之后把背包赋值为之前记录的状态,然后把后半部分背包一下,前半部分丢进去处理。
到最后一层的时候就可以直接更新答案了。
很牛逼啊,这是 (O(nklog_2n)) 。
代码
还没懂看看代码就懂了
//12252024832524
#include <bits/stdc++.h>
#define TT template<typename T>
using namespace std;
typedef long long LL;
const int MAXN = 3005;
int n,k;
LL a[MAXN][MAXN],dp[MAXN],tmp[30][MAXN],ans;
LL Read()
{
LL x = 0,f = 1; char c = getchar();
while(c > '9' || c < '0'){if(c == '-') f = -1;c = getchar();}
while(c >= '0' && c <= '9'){x = (x*10) + (c^48);c = getchar();}
return x * f;
}
TT void Put1(T x)
{
if(x > 9) Put1(x/10);
putchar(x%10^48);
}
TT void Put(T x,char c = -1)
{
if(x < 0) putchar('-'),x = -x;
Put1(x); if(c >= 0) putchar(c);
}
TT T Max(T x,T y){return x > y ? x : y;}
TT T Min(T x,T y){return x < y ? x : y;}
TT T Abs(T x){return x < 0 ? -x : x;}
void solve(int l,int r,int d)
{
if(l == r)
{
for(int i = 0;i <= a[l][0];++ i)
ans = Max(ans,dp[k-i]+(i == 0 ? 0 : a[l][i]));
return;
}
int mid = (l+r) >> 1;
for(int i = 0;i <= k;++ i) tmp[d][i] = dp[i];
for(int i = l;i <= mid;++ i)
for(int j = k;j >= a[i][0];-- j)
dp[j] = Max(dp[j],dp[j-a[i][0]]+a[i][a[i][0]]);
solve(mid+1,r,d+1);
for(int i = 0;i <= k;++ i) dp[i] = tmp[d][i];
for(int i = mid+1;i <= r;++ i)
for(int j = k;j >= a[i][0];-- j)
dp[j] = Max(dp[j],dp[j-a[i][0]]+a[i][a[i][0]]);
solve(l,mid,d+1);
}
int main()
{
// freopen("factory.in","r",stdin);
// freopen("factory.out","w",stdout);
n = Read(); k = Read();
for(int i = 1;i <= n;++ i)
{
a[i][0] = Read();
for(int j = 1;j <= Min(a[i][0],0ll+k);++ j) a[i][j] = (j == 1 ? 0 : a[i][j-1]) + Read();
for(int j = k+1;j <= a[i][0];++ j) Read();
a[i][0] = Min(a[i][0],0ll+k);
}
solve(1,n,0);
Put(ans,'
');
return 0;
}