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

点此看题面

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

二进制分解

考虑((u&v)=v)的实际意义,就是(v)中的每个(1)都出现在(u)中。(如果把(u,v)看作集合,就是(vsubseteq 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) imes2^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
",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
",f[0][n]);//输出答案
	}
}
int main()
{
	return scanf("%d%d",&k,&n),DP::Solve(),0;
}
原文地址:https://www.cnblogs.com/chenxiaoran666/p/BZOJ5003.html