把博客园图标替换成自己的图标
把博客园图标替换成自己的图标end

【BZOJ5003】与链(多重背包计数转完全背包)

点此看题面

大致题意:\(0\sim n\)\(n+1\)个点,规定当\((u\&v)=v\)时存在一条从\(u\)\(v\)的有向边(存在自环)。询问有多少条包含\(k\)个节点(同一节点多次经过算多次)的路径权值和为\(n\)

二进制分解

考虑\((u\&v)=v\)的实际意义,就是\(v\)中的每个\(1\)都出现在\(u\)中。(如果把\(u,v\)看作集合,就是\(v\subseteq u\)

因此,如果我们把路径上的点二进制分解,那么对于二进制下的每一位,这一位上的\(1\)肯定是刚开始的连续一段。

假设二进制下第\(i\)位有\(j\)\(1\),则只有恰好一种对应的方案(即最开始的\(j\)个数二进制下第\(i\)位为\(1\),之后的数二进制下第\(i\)位都为\(0\))。

多重背包

由于二进制下的每一位相互独立,我们可以把它们分别看作一个物品,其中第\(i\)个物品体积为\(2^i\),各有\(k\)个。

然后现在我们要把这些物品装入一个容积为\(n\)的背包,求恰好装满的方案数。

这显然是一个多重背包计数问题。

考虑最朴素的暴力多重背包,由于物品数是\(O(logn)\)的,总复杂度为\(O(nklogn)\)

完全背包

对于每一个物品,我们假定它可以无限选,那么这就变成了一个完全背包问题。

但真的无限选显然是不可能的,因此,我们要减去选择了超过\(k\)个该物品的方案数。

即,在做完完全背包之后,从大到小枚举每一个\(j\),令\(f_{i,j}\)减去\(f_{i,j-(k+1)\times2^i}\)

代码

#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 @ 2020-07-20 19:40  TheLostWeak  阅读(163)  评论(0编辑  收藏  举报