来自:http://www.cnblogs.com/staginner/archive/2011/12/07/2279783.html
先把剪枝放在这里,limit=min{min*min/(max-min)}。
对于一类瓶子,我们不妨设用k个瓶子去装酒,那么能够完全装下的范围就是[k*min,k*max],随着k的增大,我们发现所有相邻区间的左端点的间距是固定的,同时区间的长度又在不断增加,于是我们猜想,到某一刻时,后面的区间相互之间会有交集,这样剩下的各个区间就会覆盖掉后面所有的整数。我们设刚开始产生交集的区间为第k个,这样有k*max>=(k+1)*min,解得k>=min/(max-min),而当酒量x>=k*min的时候,就一定能全被装进去,这样就有x>=min*min/(max-min)。
最后说一说dp的过程吧,有了上面的剪枝,我们最起码能把总酒量的状态减到4500*4500,大约是2*10^7,剩下的工作就是把瓶子按不同的容积拆成max-min+1个瓶子,然后做完全背包的dp。这样我们不妨想一下,最后瓶子数乘上酒量的状态数会不会超呢?实际上由于min<=0.99max,所以即使容量是4500也实际只有不到450000个状态,远小于2*10^7,当然最大是否有可能超过450000而且最大是多少还要根据剪枝的函数来确切算一下,而且我们dp前是可以把体积重复的瓶子去掉的,由于判重的剪枝瓶子数最后能不能到比较大的水平也是个问题,所以整体的复杂度受多方因素的制约。
就剪枝min*min/(max-min)来讲,由于min*min/(max-min)<=min*min/(min/0.99-min)<450000,所以最多不会超过450000个状态。
这样总酒量大于450000的就直接剪掉了。
如何dp呢?
观察可知(min,max)区间的背包等价于以下max-min+1种背包
min,min+1...,max
进而可以转换为多重背包问题
贴一下我这无耻的代码吧:
#include <iostream> #include<cstdio> #include<cstring> using namespace std; int C,n,L[105],U[105],ans,dp[450000],vis[4510],v[20000]; int main() { int t,cas=1; scanf("%d",&t); while(t--) { if(cas!=1) puts(""); cas++; scanf("%d%d",&C,&n); C*=1000; int limit=0x3f3f3f3f; for(int i=0;i<n;i++) { scanf("%d%d",&L[i],&U[i]); limit=min(limit,L[i]*L[i]/(U[i]-L[i])); } if(C>=limit) { puts("0"); continue; } memset(dp,0,sizeof(dp)); memset(vis,0,sizeof(vis)); int m=0; for(int i=0;i<n;i++) for(int j=L[i];j<=U[i];j++) if(!vis[j]) { vis[j]=1; v[m++]=j; } for(int i=0;i<m;i++) for(int j=v[i];j<=C;j++) dp[j]=max(dp[j],dp[j-v[i]]+v[i]); printf("%d\n",C-dp[C]); } return 0; }