[提高组集训2021] 妹妹卡组
一、题目
妹妹 \(\tt Oneindark\) 给了你 \(n\) 个卡组,对于每个卡组有 \(k_i\) 个卡牌,其中第 \(j\) 个卡牌的大小是 \(j\),价值是 \(a_{i,j}\),每个卡组只能选取一张卡牌。
如果卡牌栏的大小为 \(t\),那么能获得的最大价值是多少,你需要对 \(t\in[n,\sum k_i]\) 都分别求一次,因为 \(\tt Oneindark\) 会向你撒娇,所以你需要快速把它们都求出来~~
\(n\leq 10^5,k\leq 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_i\in[-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(D\cdot (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("");
}