这道题就题意来说其实就是一道简单的记录路径的0,1背包,告诉你n个物品,每种物品只能取一次,再有q个询问,问你是否能在满足选出物品的w之和为m的情况下,满足t之和为s,若可以则任意输出一种方案。
因此我们可以设计状态,dp[i][j][k]为前i个物品选出部分,当t之和为j时,w之和为k的情况能否满足,若存在方案则为1,不存在则为0。而状态的转移方程也是很简单。
dp[i][j+t[i]][k+w[i]] = dp[i-1][j][k]。但是这样会超时,超时的原因是因为大量进行了重复的动作(即k的枚举)。因此可以用二进制来表示在当前j下可以表示的k的状态,然后通过位运算来快速进行所有k的推导,使得整个维度降低了一维,这样再记录路径就可以做了。
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long LL;
const int SIZEN = 200505;
const int M = 55;
int w[1005],t[1005];
LL dp[SIZEN];
LL has[1005];
int pre[SIZEN][55];
int ans[1000];
LL lowbit(LL x){
return x & (-x);
}
void solve(){
int n,q;
scanf("%d%d",&n,&q);
for(int i = 0 ; i < n ; i ++)
scanf("%d%d",&w[i],&t[i]);
memset(dp,0,sizeof(dp));
dp[0] = 1;
for(int i = 0 ; i < n ; i ++){
for(int j = SIZEN - 5; j >= t[i] ;j --){
LL tmp = dp[j];
dp[j] |= (dp[ j - t[i] ]<<w[i]) & ((1ll<<(M + 1)) - 1);
for(LL k = tmp ^ dp[j] ; k ; k -= lowbit(k)){
LL tt = lower_bound(has,has+M,lowbit(k)) - has;
pre[j][tt] = i;
}
}
}
int m,s;
for(int i = 0 ; i < q ; i ++){
scanf("%d%d",&m,&s);
if(!(dp[s] & (1ll<<m))) printf("No solution!\n");
else{
int cnt = 0;
while(s){
int id = pre[s][m];
ans[cnt++] = id + 1;
s -= t[id];
m -= w[id];
}
for(int i = 0 ; i < cnt - 1; i++) printf("%d ",ans[i]);
printf("%d\n",ans[cnt - 1]);
}
}
}
int main()
{
int _;
scanf("%d",&_);
for(int i = 0 ; i < M ; i++) has[i] = (1ll<<i);
while(_--) solve();
}