Loading

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

[BZOJ 5003] 与链

背包 dp

题意

思路

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

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

  1. 二进制拆分,经典做法,将每一位的个数拆成二的幂次做 \(01\) 背包,复杂度 \(O(nk\log k)\)
  2. 前缀和优化,设 \(g_i\) 为当前二的幂次的前缀和,转移有 \(f_i=g_i-g_{i-2^{k+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 @ 2024-06-29 08:29  Fire_Raku  阅读(18)  评论(0编辑  收藏  举报