題目
玄學構造題,01揹包的逆向構造。首先可以明確模數是沒用的, 我們一定可以構造出方案數小於模數的一組數據(因爲只有揹包體積w已知的條件下,可以構造出方案數爲任意值的物品數據)。這玩意兒構造的自由度太高,還有就是數不止一個,且互相之間沒啥關係,不好下手。題解的方法是構造這樣一組數據:由x個1和若干個w/2(向上取整)的大數,對於這樣一組數據,我們必須且只能選一個大數a,對總方案數的貢獻是C(x,w-a)(從x中取w-a得方案數);
令dp[i][j] 表示i個1,方案數爲j所需要的最少的大數物品個數,那麼狀態轉移方程就是:
dp[i][j+C[i][k]] = min(dp[i][j+C[i][k]],dp[i][j] + 1);
最後我們就可以記錄dp計算過程,構造出這個序列;
對於枚舉的1的個數的範圍肯定不會超過40。但是具體還能縮小,不知道怎麼縮的
#include<bits/stdc++.h>
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<algorithm>
#include<map>
#include<bitset>
#include<queue>
#define lson (rt << 1)
#define rson (rt << 1 | 1)
const int maxn = 100000+10;
const int maxm = 4e5 + 10;
const int inf_max = 0x3f3f3f;
const int mod = 1000000007;
using namespace std;
typedef long long ll;
int w,p,K;
int dp[45][20010],path[45][20010];
int C[45][45];
void pre_c() {
C[0][0] = 1;
for(int i = 1;i <= 25; ++i) {
C[i][0] = 1;
for(int j = 1;j <= 25; ++j) {
C[i][j] = C[i-1][j-1] + C[i-1][j];
}
}
}
int main()
{
pre_c();
memset(dp,inf_max,sizeof(dp));
for(int i = 0;i <= 25; ++i) dp[i][0] = 0;
for(int i = 0;i <= 25; ++i) {
for(int j = 0;j <= 20000; ++j) {
for(int k = 0;k <= i; ++k) {
if(j + C[i][k] <= 20000 && dp[i][j + C[i][k]] > dp[i][j] + 1) {
dp[i][j+C[i][k]] = dp[i][j] + 1; //dp[i][j]可以添加一個體積是w-k的大數物品得到dp[i][j+C[i][k]]這個狀態
path[i][j+C[i][k]] = k; //記錄dp過程的信息
}
}
}
}
int T;
cin>>T;
while(T--) {
cin>>w>>p>>K;
for(int i = 1;i <= 25; ++i) {
if(dp[i][K] + i <= 40) { //如果滿足條件就輸出
printf("%d\n",dp[i][K] + i);
for(int j = 1;j <= i; ++j) printf("1 "); //1的個數
while(K) { //K代表當前的方案數
int tt = path[i][K];
printf("%d ",w-tt);
K -= C[i][tt]; //少了一個大數物品體積w-tt,減去這個物品的貢獻C[i][tt];
}
printf("\n");
break;
}
}
}
return 0;
}