一、题目
妹妹 ( t Oneindark) 给了你 (n) 个卡组,对于每个卡组有 (k_i) 个卡牌,其中第 (j) 个卡牌的大小是 (j),价值是 (a_{i,j}),每个卡组只能选取一张卡牌。
如果卡牌栏的大小为 (t),那么能获得的最大价值是多少,你需要对 (tin[n,sum k_i]) 都分别求一次,因为 ( t Oneindark) 会向你撒娇,所以你需要快速把它们都求出来~~
(nleq 10^5,kleq 5)
二、解法
设 (f(i,j)) 为考虑前 (i) 个卡组大小为 (j) 的最大价值,首先有一个根本想不到的结论:把 (j) 这一维模 (12) 意义下分组后,每一组的 (dp) 值关于 (j) 有凸性,设 (D=12),证明:
首先给出引理:对于若干个和为 (24) 且在 (in{0,1,2,3,4}) 中的元素,可以划分成和为 (12) 的两组。
我们首先把卡牌的大小都减去 (1)(方便套用引理),然后考虑 (f(x-D)) 调整到 (f(x+D)) 的过程,设第 (i) 组卡牌的变化量是 (d_i),有 (sum d_i=24),且 (d_iin[-4,4])
那么我们可以把若干个 (d_i) 组合起来,使得和在 (in[0,4]) 中,然后我们把 (d_i) 划分成两组,代价变化多的一组就应用到 (f(x-D)) 到 (f(x)) 这个过程上,所以有:
[f(x)-f(x-D)geq f(x+D)-f(x) ]
知道此结论我们把第一维分治,问题变成了合并两个 (dp) 数组,那么我们花费 (O(D^2)) 枚举两个组,因为每组内部有相同的凸性,所以可以双指针合并(选增量大的那个),时间复杂度 (O(frac{len}{D})),所以总时间复杂度 (O(Dcdot (nk)log nk))
三、总结
对于可以快速合并的背包,可以用分治的方法优化。
但是这个凸性真的不太明白啊
#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;
#define int long long
const int M = 200005;
const int D = 12;
int read()
{
int x=0,f=1;char c;
while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
return x*f;
}
int n,m,k[M],sum,dp[5*M],a[M][5];
void div(int l,int r,int *dp)
{
if(l==r)
{
for(int i=0;i<k[l];i++)
dp[i]=a[l][i];
return ;
}
int mid=(l+r)>>1,ll=(mid-l+1)*5,lr=(r-mid)*5;
int f[ll+5],g[lr+5];
memset(f,-0x3f,sizeof f);
memset(g,-0x3f,sizeof g);
div(l,mid,f);div(mid+1,r,g);
for(int x=0;x<D;x++) for(int y=0;y<D;y++)
{
if(x>=ll || y>=lr) continue;
dp[x+y]=max(dp[x+y],f[x]+g[y]);
int i=x,j=y;
while(i+D<ll || j+D<lr)
{
int f1=i+D<ll,f2=j+D<lr;
if(!f1 || (f2 && g[j+D]-g[j]>f[i+D]-f[i]))
{
dp[i+j+D]=max(dp[i+j+D],f[i]+g[j+D]);
j+=D;
}
else
{
dp[i+j+D]=max(dp[i+j+D],f[i+D]+g[j]);
i+=D;
}
}
}
}
signed main()
{
freopen("fake.in","r",stdin);
freopen("fake.out","w",stdout);
n=read();
for(int i=1;i<=n;i++)
{
sum+=k[i]=read();
for(int j=0;j<k[i];j++)
a[i][j]=read();
}
memset(dp,-0x3f,sizeof dp);
div(1,n,dp);
for(int i=0;i<=sum-n;i++)
printf("%lld ",dp[i]);
puts("");
}