背包

适合有背包基础的人食用,一些经典的背包模型,做的比较少,后面再更新吧

一.需要注意的经典模板:
     详见《背包九讲》;

    划重点!

     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;
View Code

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;
View Code


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}$  ;

 

     每次都跑一遍肯定超时;
     k自然是背包的体积了,想想a,b;
     先撕烤a,b中的一个,假设b,发现普通可行性的背包里面存的只有01,十分浪费,让里面存构成这个体积的 每个方案 物品的最小b 的最大值,每次直接将m+s和f[k]比较就可以了;
     再撕烤加入a,将物品按照a排序,询问也离线了按照a排序 ,i,j两个指针指一指,枚举i(询问),将j(物品)移到最后一个a[j]<=q[i].m的物品,边移边加入f数组
    
 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;
View Code


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;
View Code

 

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;
View Code

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; 
View Code

 

 

 

 

 

 

 

 

 

 

 

 

 

大米飘香的总结:

      最近改换了博客风格,以前可能是不太舍得大米兔的风格,或者说比较倔,感觉换了风格就不太对得起博客,省选过后的半年里感觉十分绝望,停更了一段时间,竞赛学习也有点滞留期,后面想清楚了,开始复习了也觉得自己应该写点,可能很多都以专题呈现,但是我本人变化比较大。。;我现在更希望用简洁但能引导思维的语言去经营博客,可能不会生动,但我想应该还是有希望写出很好的文章,帮帮更多的人(顺便骗取访问量->_<-);

 

 

 

 

 

 

 

 

 

原文地址:https://www.cnblogs.com/Paul-Guderian/p/9552728.html