【BZOJ 5003】與鏈 (多重揹包 dp)

Fire_Raku發表於2024-06-29

【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;
}

相關文章