codeforces 1442 D Codeforces Round 681 (Div. 1, based on VK Cup 2019-2020 - Final) D
大意就是给你n组物品,这n组物品里面每组有\(t_i\)个,且他们是按照价值不降的顺序排列的。
现在允许取k个物品,每个物品必须取在数组的开头处,每个物品在被取用后就会消失。
问你最大能够拿到多少价值的物品。
其中\(n,k\leq 1500,\sum t_i \leq 1e6,a_i\leq 1e8\)
很背包吧。
可以先推导点贪心。
很明显,总是先把一个序列取完是优秀的。
当你一个序列没有取完时,就取了下一个序列,从结果上看,总是不够优秀的。末尾更大的那个序列总是值得更多的空间。
所以我们最终的答案就是,一堆完全取干净的位置,和一个没有取干净的位置。
但是并不能直接按照01背包先假设每一个序列都是一个物品,然后算出这个的答案再填补剩下的空缺。这个贪心时很明显错误的。
而且是很具有误导性的。
暴力的做法就是枚举某一个数组先不被利用,然后对剩余的数组假设他们是一个物品先进行01背包,然后再枚举使用几个空位给剩下的数组。统计出答案。
这样的做法的复杂度是\(O(n^2k)\)的,时间复杂度不符合要求。需要优化。
这里需要对背包有一定的理解。背包的第一层循环是在选择物品,第二次循环则是在处理选择这个物品对所有状态的影响。
所有,第一层循环的顺序是没有意义的,从结果上看。如果我不想要一个物品的选择出现在最终的答案里,办法就是在第一重循环遇到他的时候,跳过。这样他的影响就不会出现在dp数组的状态中。而我们现在要做的,就是尝试对每个从\(1\)到\(n\)的\(i\)计算,假如不考虑第\(i\)个物品,答案是多少。如果这个部分能够在小于\(O(nk)\)的时间昨晚,这题就搞定了。
那么,按照上面说的,我们不想要一个物品出现在最终的答案里面,办法就是在在第一重循环跳过他。
于是我们就发现了一个被反复统计的地方,对于\(\frac n 2 +1\)和\(\frac n 2 +2\)直到\(n\),其实dp的前\(\frac n 2\)个部分都是一样的,而得到的dp数组的结果也是一样的。那为什么我们不合并这一部分的运算,而是让它白白的重复这么多遍呢?
这里需要提到一个背包的理解,背包的第二重循环是处理一个确定的物品对于整个dp数组的影响。而其实我先处理那些物品是一样的,因为他们的影响都是被合法的统计了,都是正确的,我既可以先使用3,再使用1和2,也可以先使用2和3,再使用1。这些得到的结果都是和你先使用1再使用2,3是一模一样的。
我们的优化方法就是分治。对于当前状态,假设我们需要在当前的dp数组上分治区间\([l,r]\),其他部分的答案都已经被统计过了,呈现在了我们现有的dp数组中。然后我们需要讨论的就是那个不被需要的物品组在这个区间的那一个部分。是\([l,mid]\),还是\([mid+1,r]\)。
对于前面的部分,我们就先让后面的部分的所有物品在dp数组中被考虑,然后再执行\([l,mid]\)的分治,后面则是先考虑前面的物品再执行后面的分治。分治开始时需要先保存一下dp数组的状态,因为我们很容易把选择一个物品影响添加到数组中,但是想要去除他确实完全做不到的。再执行第二个分治的时候,我们需要把dp数组还原到执行第一次个分治前,这样才是正确的。
而分治的最终状态就是\(l==r\)的时候,这个时候就说明我们找到了当前被选择不被统计的那个物品组,然后尝试枚举给这个物品组多少空间,统计答案就好了。
上面的这个分治算法的时间复杂度是\(O(nlog(n)k)\) 。这是挺好证明的,如果你理解了这分治的思路的话。
其实还有分块的做法。似乎是要合并两个dp数组?突然发现我不会了。等会去看看。
然后代码挂再这里了。
#include<bits/stdc++.h>
#define ll long long
using namespace std;
inline ll read(){
ll a=0,b=1;char c=getchar();
for(;c<'0'||c>'9';c=getchar())if(c=='-')b=-1;
for(;c>='0'&&c<='9';c=getchar())a=a*10+c-'0';
return a*b;
}
ll n,k,f[3001],cnt[3001],sum[3001];
ll ans=0;
vector<ll> a[3001];
void solve(ll l,ll r)
{
ll last[3001];
if(l==r)
{
ans=max(ans,f[k]);
for(ll i=1;i<=min((ll)a[l].size(),k);i++)
{
ans=max(ans,f[k-i]+a[l][i-1]);
}
return;
}
ll mid=l+r>>1;
for(ll i=0;i<=k;i++)
{
last[i]=f[i];
}
for(ll i=l;i<=mid;i++)
{
for(ll j=k;j>=cnt[i];j--)
{
f[j]=max(f[j],f[j-cnt[i]]+sum[i]);
}
}
solve(mid+1,r);
for(ll i=0;i<=k;i++)f[i]=last[i];
for(ll i=mid+1;i<=r;i++)
{
for(ll j=k;j>=cnt[i];j--)
{
f[j]=max(f[j],f[j-cnt[i]]+sum[i]);
}
}
solve(l,mid);
// for(ll i=0;i<=k;i++)f[i]=last[i];
}
int main()
{
n=read(),k=read();
for(ll i=1;i<=n;i++)
{
cnt[i]=read();
for(ll j=0;j<cnt[i];j++)
{
ll x=read();
sum[i]+=x;
a[i].push_back(sum[i]);
}
}
solve(1,n);
cout<<ans<<endl;
return 0;
}
/*
2 4
2 6 7
3 3 4 7
*/