这道题就题意来说其实就是一道简单的记录路径的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(); }
时间: 2024-10-06 19:12:16