题目
玄学构造题,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;
}