[BZOJ 5003] 与链 (多重背包 dp)
背包 dp
题意
思路
考虑刻画 (i&j)==j
的条件,其实就是 \(j\) 是 \(i\) 在二进制位上的子集。那么路径就是不断取子集的过程。考虑按二进制上每一位考虑,那么路径上的 \(1\) 都是一段前缀。因为路径长度等于 \(k\),所以 \(1\) 的数量 \(\le k\)。
可以转化为多重背包问题,其中每一位上的 \(1\) 不能取超过 \(k\) 次。这时可以有以下实现方法:
- 二进制拆分,经典做法,将每一位的个数拆成二的幂次做 \(01\) 背包,复杂度 \(O(nk\log k)\)。
- 前缀和优化,设 \(g_i\) 为当前二的幂次的前缀和,转移有 \(f_i=g_i-g_{i-2^{k+1}}\)。复杂度 \(O(nk)\)。
- 转化为完全背包,先按完全背包做,然后去掉超过的转移。复杂度 \(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;
}