适合有背包基础的人食用,一些经典的背包模型,做的比较少,后面再更新吧;
一.需要注意的经典模板:
详见《背包九讲》;
划重点!
1 . 树形依赖背包:
有 直接dfs 和 转dfs序 两种实现方法,我比较习惯后面一种(蒟蒻不会第一种。。),写起来简洁一点:将原树转成dfs序 dfn[i]存第dfs序为i的节点编号,记录每个点的子树大小sz[],再从dfs序后往前做背包,考虑选和不选当前点,选的先强制塞进去,从f[i+1]转移,否则从f[i+sz[dfn[i]]]转移;
2 . 多重背包的单调队列优化:
画柿子:f[j]=max{f[j] , f[j-k*c[i]] + k*w[i]};
令j=a*c[i]+b;
f[j]=max{f[j] , f[(a-k)*c[i] + b] + k*w[i]};
f[j]=max{f[j] , f[(a-k)*c[i] + b] - (a-k)*w[i] + a*w[i]}
f[j]=max{f[j] , f[(a-k)*c[i] + b] – (a-k)*w[i]}+a*w[i];
对于一个确定的j,a*w[i]是确定的 , 取值只和(a-k)有关;
枚举里面的b,所有的j的b一样的话且a的系数递增即连续,就是a+b,2a+b,3a+b … k[ji]a+b,用一个单调队列维护 f[k[j1]*a+b]-k[j1]*w[i],转移j2的时候再加上k[j2]*w[i](j1<=j2),再塞进去,顺便记录下k,k[j2]-k[j1]大于物品个数就出队;
通俗的理解是,对于余数相同的排排站,后面的一定可以从前面添几个物品,用一个差的思想。。。。。。。。(已词穷。。。)
二.一些题:
bzoj2287 消失之物 (统计方案背包的删除)
题目大意:有n个物品,fi,j表示删掉第i个物品剩下的n-1个物品可以组成体积j的方案数,求所有的fi,j并mod10输出一个矩阵;
第一眼感觉是分治。。。
先求出n个物品的fj数组(组成j体积的方案数),考虑删去一个物品的f数组如何变化,我们加入一个物品时更新方案是倒着枚举f[j]+=f[j-c[i]];现在假设设 1- i-1 都已经被还原那么 f[j] –= f[j-c[i]] 即可,所以顺着枚举并按照上述方式还原;
我觉得,统计最大价值的应该不能删除;
可以看下代码;
1 #include<cstdio> 2 #include<iostream> 3 using namespace std; 4 const int N=300001; 5 int n,m,f[N],tmp[N],a[N]; 6 int main() 7 { freopen("bzoj2287.in","r",stdin); 8 freopen("bzoj2287.out","w",stdout); 9 scanf("%d%d",&n,&m); 10 f[0]=1; 11 for(int i=1,x;i<=n;i++){ 12 scanf("%d",&x);a[i]=x; 13 for(int j=m;j>=x;j--)f[j]=(f[j]+f[j-x])%10; 14 } 15 for(int i=1;i<=n;i++){ 16 memcpy(tmp,f,sizeof(f)); 17 for(int j=a[i];j<=m;j++)tmp[j]=(tmp[j]-tmp[j-a[i]]+10)%10; 18 for(int j=1;j<=m;j++)putchar(tmp[j]+'0'); 19 puts(""); 20 } 21 return 0; 22 }//by tkys_Austin;
poj3093 Margaritas on the River Walk (枚举+带强制的背包)
题意:多组输入,每组给定物品数和背包容量以及接下来每个物品的体积,问有多少种方案,使得装入一些物品后,无法装入剩下的任意一个物品。
无法装入的意思是剩下的物品任何一个都比现在剩下的体积小,可以枚举剩下的体积最大物品,不放入背包,比这个物品体积小的一定被放进了背包,比这个体积大的可以放也可以不放,从小到大排序,从前往后扫一遍,把1 - i-1体积加起来看成一个物品强制加进去,从后往前扫一遍i+1 - n做背包和1 - i-1这整个物品合并,统计比当前体积小的f答案;
应该是可以处理体积相同的情况的,比如样例;
1 #include<cstdio> 2 #include<iostream> 3 #include<algorithm> 4 using namespace std; 5 const int N=3010; 6 typedef long long ll; 7 int T,n,m,a[N]; 8 ll sum[N],f[N]; 9 int main() 10 { freopen("poj3093.in","r",stdin); 11 freopen("poj3093.out","w",stdout); 12 scanf("%d",&T);int Case=0; 13 while(T--){ 14 scanf("%d%d",&n,&m); 15 for(int i=1;i<=n;i++)scanf("%d",&a[i]),sum[i]=0; 16 sort(a+1,a+n+1); 17 for(int i=1;i<=n;i++)sum[i]=a[i]+sum[i-1]; 18 for(int i=1;i<=m;i++)f[i]=0; 19 f[0]=1; 20 ll ans=0; 21 for(int i=n;i;i--){ 22 for(int j=m;j;j--){ 23 if(j+a[i]>m&&j>=sum[i-1])ans+=f[j-sum[i-1]]; 24 if(j>=a[i])f[j]+=f[j-a[i]]; 25 } 26 } 27 printf("%d %lld ",++Case,ans); 28 } 29 return 0; 30 }//by tkys_Austin;
bzoj2794 Cloakroom (二维限制的背包,离线做法)
题目大意:n个物品属性a[i],b[i],c[i](a[i]<b[i]),q个询问m,k,s询问是否能选出一些物品使得a[i]<=m,b[i]>m+s,$sum{c[i]=k}$ ;
1 #include<cstdio> 2 #include<iostream> 3 #include<cstring> 4 #include<algorithm> 5 using namespace std; 6 const int N=1000010,inf=0x3f3f3f3f; 7 int n,m,f[N],ans[N],mxk; 8 struct data{ 9 int id,x,y,c; 10 bool operator <(const data&A)const{return x<A.x;} 11 }Q[N],A[N]; 12 int main() 13 { freopen("bzoj2794.in","r",stdin); 14 freopen("bzoj2794.out","w",stdout); 15 scanf("%d",&n); 16 for(int i=1,x,y,z;i<=n;i++){ 17 scanf("%d%d%d",&z,&x,&y); 18 A[i]=(data){i,x,y,z}; 19 } 20 scanf("%d",&m); 21 for(int i=1,x,y,z;i<=m;i++){ 22 scanf("%d%d%d",&x,&y,&z); 23 Q[i]=(data){i,x,x+z,y}; 24 mxk=max(y,mxk); 25 } 26 sort(A+1,A+n+1); 27 sort(Q+1,Q+m+1); 28 f[0]=inf; 29 for(int i=1,j=1;i<=m;i++){ 30 while(j<=n&&A[j].x<=Q[i].x){ 31 for(int I=mxk;I>=A[j].c;I--)f[I]=max(f[I],min(A[j].y,f[I-A[j].c])); 32 j++; 33 } 34 if(Q[i].y<f[Q[i].c])ans[Q[i].id]=1;else ans[Q[i].id]=0; 35 } 36 for(int i=1;i<=m;i++)ans[i]?puts("TAK"):puts("NIE"); 37 return 0; 38 }//by tkys_Austin;
bzoj1190 梦幻岛宝珠 (二进制分层背包)
题目大意:n<=100个物品,求体积不超过w<=2^30的最大价值,每个物品的体积可以写成a*(2^b)的形式且b<=30,a<=10;
主要是w太大,但是a*(2^b)的形式很好,并且b的值有限,同时$ sum{a} $也很有限最大是1000,暂且随网上dalao的叫法叫做二进制分层背包吧;
将输入的体积按b分组,对每一组内做一个普通的背包;
然后f[i][j]表示j*2^i + w&((2^i)-1) 的体积的最大价值,或者说做到第0 – i-1位体积都满足w的限制,第i位层上体积为j的最大价值;
先将f[i][j]初始化为每层单独的背包;
层与层之间 f[i][j]=min{ f[i][k]+f[i-1][min( (k<<1) | ((w>>(i-1))&1 ) , 1000)] };
由于此题只要求<=w所以可以对最大的j取min;
但如果只能等于w的话大于直接取成inf,同时层内背包f[0]=0,其他全是inf(之前一直没想清楚这点);
代码中的w[]是用来递推每一位的j上界,替换了1000;
1 #include<cstdio> 2 #include<iostream> 3 #include<cstring> 4 using namespace std; 5 const int N=110,M=1010; 6 typedef long long ll; 7 int n,m,p[31][N],q[31][N],cnt[31],w[31]; 8 ll f[31][M]; 9 int main() 10 { freopen("bzoj1190.in","r",stdin); 11 freopen("bzoj1190.out","w",stdout); 12 while(scanf("%d%d",&n,&m)!=EOF&&n!=-1){ 13 memset(cnt,0,sizeof(cnt)); 14 memset(w,0,sizeof(w)); 15 for(int i=1;i<=n;i++){ 16 int x,y=0,val;scanf("%d%d",&x,&val); 17 while(!(x&1))x>>=1,y++; 18 p[y][++cnt[y]]=x;q[y][cnt[y]]=val; 19 w[y]+=x; 20 } 21 int len=0;while(m>>(len+1))len++; 22 memset(f,0,sizeof(f)); 23 for(int i=0;i<=len;i++) 24 for(int j=1;j<=cnt[i];j++) 25 for(int k=w[i];k>=p[i][j];k--)f[i][k]=max(f[i][k],f[i][k-p[i][j]]+q[i][j]); // 26 for(int i=1;i<=len;i++){ 27 w[i]+=(w[i-1]+1)>>1; 28 for(int j=w[i];j>=0;j--) 29 for(int k=0;k<=j;k++)f[i][j]=max(f[i][j],f[i][j-k]+f[i-1][min((k<<1)|((m>>(i-1))&1),w[i-1])]); 30 } 31 cout<<f[len][1]<<endl; 32 } 33 return 0; 34 }//by tkys_Austin;
bzoj4753 最佳团体 (01分数规划+树形依赖背包)
题目大意:一个树形图,每个节点有价值和代价,只有当父亲选了才可以选,求最优的性价比;
01分数规划套一个树形依赖背包就好,懒癌晚期,不再赘述;
要求的答案好像比较诡异,记不得哪个是分子分母了。。。;
1 #include<cstdio> 2 #include<iostream> 3 #include<cstring> 4 #include<vector> 5 #include<cmath> 6 using namespace std; 7 const int N=2505,inf=0x3f3f3f3f; 8 int n,k,dfn[N],idx,sz[N],v[N],w[N],r[N]; 9 double f[N][N]; 10 vector<int>E[N]; 11 void dfs(int u){ 12 sz[u]=1;dfn[++idx]=u; 13 for(int i=0;i<(int)E[u].size();i++){ 14 int v=E[u][i]; 15 dfs(v); 16 sz[u]+=sz[v]; 17 } 18 } 19 int main() 20 { //freopen("bzoj4753.in","r",stdin); 21 //freopen("bzoj4753.out","w",stdout); 22 scanf("%d%d",&k,&n); 23 for(int i=1;i<=n;i++){ 24 scanf("%d%d%d",&v[i],&w[i],&r[i]); 25 E[r[i]].push_back(i); 26 } 27 dfs(0); 28 for(int i=1;i<=k;i++)f[n+2][i]=-inf; 29 double l=0,r=10000; 30 while(fabs(l-r)>1e-4){ 31 double mid=(l+r)/2; 32 for(int i=n+1;i>1;i--){ 33 int u=dfn[i]; 34 for(int j=1;j<=k;j++){ 35 f[i][j]=max(f[i+1][j-1]+w[u]-v[u]*mid,f[i+sz[u]][j]); 36 } 37 } 38 if(f[2][k]>=0)l=mid; 39 else r=mid; 40 } 41 printf("%.3lf ",l); 42 return 0; 43 }//by tkys_Austin;
bzoj4182 shopping (单调队列优化多重背包+点分治)
题目大意:每个节点有库存d[i]个物品,每个物品价格c[i],价值w[i],买东西的节点一定是个联通块,求最大价值;
连通块的话一定有一个分治中心在连通块上,对每个分治中心做一次树形依赖的多重背包;
还是做dfs序,有一点注意就是多重背包要选的话就要强制塞一个物品再从f[i+1]转移;
可以单调队列优化,也可以二分制分组优化;
1 #include<cstdio> 2 #include<iostream> 3 #include<cstring> 4 #include<algorithm> 5 using namespace std; 6 const int N=510,M=4010; 7 int o,n,m,hd[N],w[N],c[N],d[N],mx[N],sz[N],size,rt,vis[N],f[N][M],ans,tot,dfn[N],ed[N],head,tail,g[M]; 8 struct node{int x,j;}q[M]; 9 struct Edge{int v,nt;}E[N<<1]; 10 void Adde(int u,int v){E[o]=(Edge){v,hd[u]};hd[u]=o++;}// 11 void adde(int u,int v){Adde(u,v);Adde(v,u);}// 12 void get_rt(int u,int fa){ 13 sz[u]=1;mx[u]=0; 14 for(int i=hd[u];~i;i=E[i].nt){ 15 int v=E[i].v; 16 if(v==fa||vis[v])continue; 17 get_rt(v,u); 18 sz[u]+=sz[v]; 19 mx[u]=max(mx[u],sz[v]); 20 } 21 mx[u]=max(mx[u],size-sz[u]); 22 if(mx[u]<mx[rt])rt=u; 23 }// 24 void dfs(int u,int fa){ 25 dfn[++tot]=u; 26 for(int i=hd[u];~i;i=E[i].nt){ 27 int v=E[i].v;if(v==fa||vis[v])continue; 28 dfs(v,u); 29 } 30 ed[u]=tot; 31 }// 32 void solve(int u){ 33 vis[u]=1; 34 tot=0;dfs(rt,0);// 35 int pre=size;// 36 for(int i=0;i<=m;i++)f[tot+1][i]=0;// 37 for(int I=tot;I;I--){ 38 int x=dfn[I]; 39 for(int i=0;i<=m;i++)f[I][i]=f[ed[x]+1][i];//,g[i]=0; 40 //for(int i=0;i<=m-c[x];i++)g[i+c[x]]=f[I+1][i]+w[x]; 41 for(int i=0;i<c[x];i++){ 42 head=tail=0;int cnt=0; 43 for(int j=c[x]+i;j<=m;j+=c[x],cnt++){ 44 while(head<tail&&cnt-q[head+1].j>d[x])head++; 45 int T=f[I+1][j-c[x]]+w[x]-w[x]*cnt; 46 while(head<tail&&q[tail].x<T)tail--; 47 q[++tail]=(node){T,cnt}; 48 f[I][j]=max(f[I][j],q[head+1].x+cnt*w[x]); 49 } 50 } 51 }// 52 for(int i=0;i<=m;i++)ans=max(ans,f[1][i]); 53 for(int i=hd[u],v=E[i].v;~i;i=E[i].nt,v=E[i].v){ 54 if(vis[v])continue; 55 size=sz[u]>sz[v]?sz[v]:pre-sz[u]; 56 rt=0;get_rt(v,0); 57 solve(rt); 58 }// 59 }// 60 int main() 61 { freopen("bzoj4182.in","r",stdin); 62 freopen("bzoj4182.out","w",stdout); 63 int T;scanf("%d",&T); 64 while(T--){ 65 scanf("%d%d",&n,&m); 66 for(int i=1;i<=n;i++)scanf("%d",&w[i]),vis[i]=0; 67 for(int i=1;i<=n;i++)scanf("%d",&c[i]),hd[i]=-1; 68 for(int i=1;i<=n;i++)scanf("%d",&d[i]),d[i]--; 69 o=0;for(int i=1,x,y;i<n;i++)scanf("%d%d",&x,&y),adde(x,y);// 70 ans=0;size=mx[rt=0]=n;// 71 get_rt(1,0);solve(rt);// 72 cout<<ans<<endl;// 73 } // 74 return 0; 75 }//by tkys_Austin;
大米飘香的总结:
最近改换了博客风格,以前可能是不太舍得大米兔的风格,或者说比较倔,感觉换了风格就不太对得起博客,省选过后的半年里感觉十分绝望,停更了一段时间,竞赛学习也有点滞留期,后面想清楚了,开始复习了也觉得自己应该写点,可能很多都以专题呈现,但是我本人变化比较大。。;我现在更希望用简洁但能引导思维的语言去经营博客,可能不会生动,但我想应该还是有希望写出很好的文章,帮帮更多的人(顺便骗取访问量->_<-);
当夜空又出现小流星 请别着急闭上你眼睛
许的小心愿 帮你实现 只要我听得见 ------------汪苏泷《小流星》