[BZOJ 5003] 与链 (多重背包 dp)

[BZOJ 5003] 与链

背包 dp

题意

思路

考虑刻画 (i&j)==j 的条件,其实就是 ji 在二进制位上的子集。那么路径就是不断取子集的过程。考虑按二进制上每一位考虑,那么路径上的 1 都是一段前缀。因为路径长度等于 k,所以 1 的数量 k

可以转化为多重背包问题,其中每一位上的 1 不能取超过 k 次。这时可以有以下实现方法:

  1. 二进制拆分,经典做法,将每一位的个数拆成二的幂次做 01 背包,复杂度 O(nklogk)
  2. 前缀和优化,设 gi 为当前二的幂次的前缀和,转移有 fi=gigi2k+1。复杂度 O(nk)
  3. 转化为完全背包,先按完全背包做,然后去掉超过的转移。复杂度 O(nk)

贴一下大佬 TheLostWeak 的代码:

#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 100000
#define LN 20
#define X 1000000009
#define Inc(x,y) ((x+=(y))>=X&&(x-=X))
using namespace std;
int n,k;
namespace BF//暴力多重背包计数
{
	#define BN 1000
	int f[LN+5][BN+5];
	I void Solve()
	{
		RI i,j,p;for(f[LN+1][0]=1,i=LN;~i;--i) for(j=0;j<=k&&(j<<i)<=n;++j)
			for(p=0;p+(j<<i)<=n;++p) Inc(f[i][p+(j<<i)],f[i+1][p]);
		printf("%d\n",f[0][n]);
	}
}
namespace DP//多重背包计数转完全背包
{
	int f[LN+5][N+5];
	I void Solve()
	{
		RI i,j,p;for(f[LN+1][0]=1,i=LN;~i;--i)//枚举物品
		{
			for(j=0,p=1<<i;j<=n;++j) Inc(f[i][j],f[i+1][j]),j+p<=n&&Inc(f[i][j+p],f[i][j]);//完全背包
			for(j=n;j>=1LL*(k+1)*p;--j) Inc(f[i][j],X-f[i][j-(k+1)*p]);//减去不合法情况
		}printf("%d\n",f[0][n]);//输出答案
	}
}
int main()
{
	return scanf("%d%d",&k,&n),DP::Solve(),0;
}

posted @   Fire_Raku  阅读(22)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具
more_horiz
keyboard_arrow_up dark_mode palette
选择主题
点击右上角即可分享
微信分享提示