[提高组集训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("");
}
posted @ 2021-10-04 21:58  C202044zxy  阅读(159)  评论(2编辑  收藏  举报