https://codeforces.com/gym/101064/problem/L
背包容量S特别大,但是每个物品重量相比之下比较小
令mx表示所有物品中重量最大的,把S拆分成两部分,S=A+B 且 |A-B|<=mx
因为如果A和B的重量相差超过mx,可以把mx从重的那一部分放到轻的那一部分
即|A-(S-A)|<=mx
所以 S/2-mx/2 <= A <= S/2+mx/2
同理为了求出 f(S/2-mx/2)到 f(S/2+mx/2)
就要求出 f(S/4- mx*3/4)到 f(S/4+mx*3/4)
这样一直往下
整个mx的偏移范围不会超过[-mx,mx]
所以所有需要求的就是 f(S/2^k-mx)到 f(S/2^k+mx)
状态数为log(S)*mx*2个
每个状态枚举mx的偏移范围平方求即可
复杂度为log(S)*mx^2
#include<bits/stdc++.h> using namespace std; typedef long long LL; LL dp[101][4001],f[4001]; int low[101]; int w[1001],c[1001]; int main() { int T,n,m,mx,cnt; scanf("%d%d",&n,&m); mx=0; for(int i=1;i<=n;++i) { scanf("%d%d",&w[i],&c[i]); mx=max(mx,w[i]); } cnt=0; while(m) { low[++cnt]=m-mx; m/=2; } memset(f,0,sizeof(f)); for(int i=1;i<=n;++i) for(int j=w[i];j<=2*mx;++j) f[j]=max(f[j],f[j-w[i]]+c[i]); for(int i=cnt;i;--i) for(int j=low[i];j<=low[i]+mx*2;++j) if(j<=2*mx) dp[i][j-low[i]]=f[j]; else for(int k=(j-mx)/2;k<=j/2;++k) dp[i][j-low[i]]=max(dp[i][j-low[i]],dp[i+1][j-k-low[i+1]]+dp[i+1][k-low[i+1]]); printf("%lld",dp[1][mx]); }