背包问题培训学案.docx

上传人:b****8 文档编号:30376522 上传时间:2023-08-14 格式:DOCX 页数:17 大小:37.12KB
下载 相关 举报
背包问题培训学案.docx_第1页
第1页 / 共17页
背包问题培训学案.docx_第2页
第2页 / 共17页
背包问题培训学案.docx_第3页
第3页 / 共17页
背包问题培训学案.docx_第4页
第4页 / 共17页
背包问题培训学案.docx_第5页
第5页 / 共17页
点击查看更多>>
下载资源
资源描述

背包问题培训学案.docx

《背包问题培训学案.docx》由会员分享,可在线阅读,更多相关《背包问题培训学案.docx(17页珍藏版)》请在冰豆网上搜索。

背包问题培训学案.docx

背包问题培训学案

背包问题

(2)

2010年3月11日晚

培训内容:

1、砝码称重的背包解法。

2、SubsetSums集合的背包解法。

3、数字游戏。

4、滚动数组的应用。

砝码称重的背包解法

【问题分析】11122333

把问题稍做一个改动,已知a1+a2+a3+a4+a5+a6个砝码的重量w[i],w[i]∈{1,2,3,5,10,20}其中砝码重量可以相等,求用这些砝码可称出的不同重量的个数。

这样一改就是经典的0/1背包问题的简化版了。

把a1个砝码看成0/1背包中的第1个物品,重量与价值均为a1*1。

把a2个砝码看成0/1背包中的第2个物品,重量与价值均为a2*2。

只是要注意这个题目不是求最大载重量,是统计所有的可称出的重量的个数。

programfmcz;

constw:

array[1..6]of1..20=(1,2,3,5,10,20);

maxll=1001;

var

i,j:

longint;

a,b:

array[1..10]oflongint;

f:

array[1..1001]oflongint;

begin

fori:

=1to6do

begin

read(a[i]);

b[i]:

=a[i]*w[i];

end;

readln;

fori:

=1to6do

begin

forj:

=maxlldowntob[i]do

begin

iff[j-b[i]]+b[i]>f[j]thenf[j]:

=f[j-b[i]]+b[i];{体积与价值相同}

end;

end;

writeln(f[maxll]);{maxll能达到多少就有多少种重量}

end.

砝码称重的测试数据如下:

4.1110000Total=3

4.2220000Total=6

4.3103000Total=7

4.4340500Total=36

4.5222222Total=82

4.6032745Total=185

4.7063421Total=79

4.8123456Total=204

4.8654321Total=83

4.101010101011Total=140

SubsetSums集合背包解法

SubsetSums

集合

对于从1到N的连续整集合合,能划分成两个子集合,且保证每个集合的数字和是相等的。

举个例子,如果N=3,对于{1,2,3}能划分成两个子集合,他们每个的所有数字和是相等的:

∙{3}and{1,2}

这是唯一一种分发(交换集合位置被认为是同一种划分方案,因此不会增加划分方案总数)

如果N=7,有四种方法能划分集合{1,2,3,4,5,6,7},每一种分发的子集合各数字和是相等的:

∙{1,6,7}and{2,3,4,5}{注1+6+7=2+3+4+5}

∙{2,5,7}and{1,3,4,6}

∙{3,4,7}and{1,2,5,6}

∙{1,2,4,7}and{3,5,6}

给出N,你的程序应该输出划分方案总数,如果不存在这样的划分方案,则输出0。

程序不能预存结果直接输出。

PROGRAMNAME:

subset

INPUTFORMAT

输入文件只有一行,且只有一个整数N

SAMPLEINPUT(filesubset.in)

7

OUTPUTFORMAT

输出划分方案总数,如果不存在则输出0。

SAMPLEOUTPUT(filesubset.out)

4

当1到n的总和为奇数时,一定没有一种方案,可以直接输出0,否则把总和div2当作背包的容量,用1到n个数去做0/1背包,把每一种情况加起来,状态方程:

f[j]=f[j]+f[j-i],由于n个数都使用了两次,所以情况总数也就是答案的2倍,所以输出时div2就可以了。

不知道为什么的请看下面:

如n=3时,s=3;集合有[{1,2}和{3},{3}和{1,2}]这样就重复了,所以div2。

f[j]集合的是由{j-i}和{i}组合的.f[j-i]有几种情况,那j-i和i的组合就有几种情况了,这是加法原理。

}

programsubset;

varn,i,j:

longint;

s:

int64;

st:

array[0..390]ofint64;

begin

 assign(input,'subset.in');reset(input);

assign(output,'subset.out');rewrite(output);

  whilenoteofdo

begin

     read(n);

    s:

=(n+1)*nshr1;

    ifsand1=1thenwriteln(0)

       else

      begin

           s:

=sshr1;

           fori:

=1tondo

          forj:

=sdowntoido

               f[j]:

=f[j]+f[j-i];

           writeln(f[s]shr1);

       end;

   end;

  close(input);close(output);

end.

varf:

array[0..100000]ofint64;

   i,n,m,j:

longint;

begin

assign(input,'subset.in');

reset(input);

assign(output,'subset.out');

rewrite(output);

readln(n);

close(input);

m:

=(1+n)*ndiv2;

ifmmod2=1thenbeginwriteln('0');close(output);halt;end;

m:

=mdiv2;

f[0]:

=1;

fori:

=1tondo

begin

forj:

=m-idownto0do

  f[i+j]:

=f[i+j]+f[j];{f[m]=f[m]+f[m-1]f[m-1]=f[m-1]+f[m-2]}

end;

writeln(f[m]div2);

close(output);

end.

动态规划一般解决两类问题,一类是最优化问题,就是问你最大价值最小数什么的,另一类是方案总数问题。

滚动数组

滚动数组就是动态规划时反复利用已开辟的空间,丢弃大量无用数组的方法

作用是大规模动规时省内存

常用于DP之中,在DP过程中,我们在由一个状态转向另一个状态时,很可能之前存储的某些状态信息就已经无用了,例如在01背包问题中,从理解角度讲我们应开DP[i][j]的二维数组,第一维我们存处理到第几个物品,也就是阶段了,第二维存储容量,但是我们获得DP[i],只需使用DP[i-1]的信息,DP[i-k],k>1都成了无用空间,因此我们可以将数组开成一维就行,迭代更新数组中内容,滚动数组也是这个原理,目的也一样,不过这时候的问题常常是不可能缩成一维的了,比如一个DP[i][j]需要由DP[i-1][k],DP[i-2][k]决定,i

不知道怎么说?

举个例子

d[0]d[1]d[2]d[3]d[4]d[5]d[6]d[7]d[8]

112358132134

d:

array[1..100]ofinteger;

d[0]=1;d[1]=1;

for(i=2;i<100;i++)

d[i]=d[i-1]+d[i-2];

printf("%d",d[99]);

上面这个循环d[i]只依赖于前两个数据d[i-1]和d[i-2];

为了节约空间用滚动数组的做法

d:

array[1..100]ofinteger;

d[0]d[1]d[2]d[0]d[1]d[2]d[0]d[1]d[2]

112358132134

d[0]=1;d[1]=1;

for(i=2;i<100;i++)

d[i%3]=d[(i-1)%3]+d[(i-2)%3];

printf("%d",d[99%3]);

注意上面的取余运算,我们成功地只保留了需要的最后3个解,数组好象在“滚动”一样,所以叫滚动数组

对于二维也可以用(代码可能不太正确和完善,但是可以理解例子):

inti,j,d[100][100];

for(i=1;i<100;i++)

for(j=0;j<100;j++)

d[i][j]=d[i-1][j]+d[i][j-1];

上面的d[i][j]只依赖于d[i-1][j],d[i][j-1];

运用滚动数组

inti,,j,d[2][100];

for(i=1;i<100;i++)

for(j=0;j<100;j++)

d[i%2][j]=d[(i-1)%2][j]+d[i%2][j-1];

光光的作业(homework)

[问题描述]

光光上了高中,科目增多了。

在长假里,光光的老师们都非常严厉,都给他布置了一定量的作业。

假期里,光光一共有的时间是k小时。

在长假前,老师们一共给光光布置了n份作业,第i份作业需要的时间是ti小时。

但是由于老师们互相不商量,因此光光有可能不能完成老师的作业。

当可能不能完成老师的作业时,光光就事后去向老师说明,然后被老师批评一顿了事。

对于一件作业,只有2种情况:

完成或者不完成(快要完成也算不完成)。

如果没完成,受到批评是天经地义的。

但是,不同的作业对于光光来说,批评的力度是不同的。

第i件作业如果没完成,就要受到pi个单位的批评。

多次这样之后,光光想要在长假前就知道他至少会受到多少个单位的批评。

你能帮助他吗?

[输入]

输入文件homework.in包含以下内容:

第一行只有一个数字k。

第二行只有一个数字n。

接下来n行,每行两个数字,分别是ti和pi,两个数字之间用一个空格分开。

100%的数据中,k<=100000,ti<=10000,pi<=10000;

30%的数据中,n<=20;

100%的数据中,n<=500。

[输出]

输出文件homework.out仅包含一行,是一个数字,代表了光光最少受到的批评。

[样例]

homework.in

5

3

26

13

47

homework.out

6

滚动数组例题:

var

 f:

array[0..1,0..100000]oflongint;

 t,p:

array[1..500]ofinteger;

 k,n,i,j,c,c2,pall:

longint;

begin

 //assign(input,'homework.in');reset(input);

 //assign(output,'homework.out');rewrte(output);

 readln(k);

 readln(n);

 pall:

=0;

 fillchar(f,sizeof(f),0);

 fori:

=1tondobegin

  readln(t[i],p[i]);

  pall:

=pall+p[i];

  forj:

=t[i]tokdo

   iff[0,j]

=p[i];

 end;

 c:

=0;

 fori:

=1tondobegin

  c:

=1-c;

  forj:

=1tokdobegin

   f[c,j]:

=f[c,j-1];

   ifj-t[i]>0then

    iff[1-c,j-t[i]]+p[i]>f[c,j]thenf[c,j]:

=f[1-c,j-t[i]]+p[i];(mod2)

  end;

 end;

 writeln(pall-f[c,k]);

 //close(input);close(output);

end.

1、P34:

数字游戏

要求:

能读懂该程序;能回答该题后面提出的一个问题:

把两阶段的程序段合并成一段。

思考:

如果要将ai、ai+1、ai+2……、aj-1、aj分成p部分,怎样分?

1≤i≤n

1≤j≤n

i≤k≤j-1

将ai、ai+1……、ak、ak+1、ak+2……aj

分成p-1部分分成第p部分

比如:

把1、2、3、4、5、6分成2部分,

123456

第m部分

如果ai、ai+1……、ak、ak+1、ak+2……aj不在一条直线上,而是在圆周上,怎么划分呢?

m-1部分由i..j组成。

第m部分由1..i-1和j+1..n组成。

m-1部分

fori:

=1tondo{计算一部分内的数和对10的模的所有可能情况}

forj:

=i+1tondo

begin

g[i,j]:

=(g[i,j-1]+g[j,j])mod10;

fmax1[i,j]:

=g[i,j];

fmin1[i,j]:

=g[i,j];

end;{for}

forp:

=2tom-1do{阶段:

递推计算划分2部分…m-1部分的结果值}

begin

fillchar(fmax,sizeof(fmax),0);{划分p部分的状态转移方程初始化}

fillchar(fmin,sizeof(fmin),$FF);

fori:

=1tondo{状态:

枚举被划分为p部分的数字区间}

forj:

=itondo

fork:

=itoj-1do{决策:

ai..ak被划分成p-1部分}

begin

iffmax1[i,k]*g[k+1,j]>fmax[i,j]then

{计算将ai、ai+1、…aj划分成p个部分的状态转移方程}

fmax[i,j]:

=fmax1[i,k]*g[k+1,j];

if(fmin1[i,k]>=0)and((fmin1[i,k]*g[k+1,j]

fmin[i,j]:

=fmin1[i,k]*g[k+1,j];

end;{for}

fmin1:

=fmin;fmax1:

=fmax;

end;{for}

max:

=0;min:

=maxlongint;{将a1、a2、…an划分成m个部分的最大值和最小值初始化}

ifm=1then{计算n个数划分成一部分的最大值和最小值}

beginmax:

=g[1,n];min:

=g[1,n];end{then}

elsefori:

=1tondo{将a1…ai-1、aj+1…an设为第m部分,计算最大值和最小值}

forj:

=itondo

if(i<>1)or(j<>n)then

begin

if(g[1,i-1]+g[j+1,n])mod10*fmax1[i,j]>maxthen

max:

=(g[1,i-1]+g[j+1,n])mod10*fmax1[i,j];

if(fmin1[i,j]>=0)and((g[1,i-1]+g[j+1,n])mod10*fmin1[i,j]

min:

=(g[1,i-1]+g[j+1,n])mod10*fmin1[i,j];

end;{then}

writeln(min);writeln(max);{输出最小值和最大值}

本题的计算过程分两个阶段

第一个阶段:

将圆周上的n个数排成一个序列,计算ai、ai+1、…aj划分成m-1个部分的最大值fmax1[i,j]和最小值fmin1[i,j];

第二个阶段:

将序列首尾相接。

枚举第m部分的所有可能情况,在fmax1和fmin1的基础上,计算圆周上的n个数划分成m个部分的最大值max和最小值min。

由于是一个圈,所以要从1-n中的每个点打断进行DP,最后统计最大最小值

思考:

能否将两个阶段合并,用一个状态转移方程来解决呢?

请修改程序。

 

6mod10=(-4)mod10=6

将N个数拉成2N个数的链,秒杀

vari,j,k,m,n,l,max,min,value:

longint;

   a:

array[1..50]oflongint;

   sum:

array[0..100]oflongint;

   f,g:

array[1..50,1..10]oflongint;

functionmin1(i,j:

longint):

longint;

begin

 ifi

 exit(j);

end;

functionget(i,j:

longint):

longint;

2

begin

 get:

=sum[j]-sum[i-1];

 ifget<0theninc(get,100000000);

 get:

=getmod10;

end;

4

-1

begin

 max:

=-maxlongint;

 min:

=maxlongint;

 readln(n,m);

3

 fori:

=1tondo

   begin

read(a[i]);

inc(sum[i],sum[i-1]+a[i]);(sum[1]=2sum[2]=1sum[3]=4sum[4]=8)

end;

  fori:

=1tondo

inc(sum[i+n],sum[i+n-1]+a[i]);

  fori:

=1tondo

 begin

     value:

=get(i,i);

     f[1,1]:

=value;

     g[1,1]:

=value;

     forj:

=2tondo

       begin

         value:

=get(i,i+j-1);

         f[j,1]:

=value;

         g[j,1]:

=value;

         fork:

=2tomin1(j,m)do

           begin

             f[j,k]:

=-maxlongint;

             g[j,k]:

=maxlongint;

             forl:

=1toj-1do

               begin

                 value:

=get(l+i,j+i-1);

                 ifvalue*f[l,k-1]>f[j,k]thenf[j,k]:

=value*f[l,k-1];

                 ifvalue*g[l,k-1]

=value*f[l,k-1]

               end;

          end;

      end;

    ifmax

=f[n,m];

    ifmin>g[n,m]thenmin:

=g[n,m];

 end;

writeln(min);

 writeln(max);

end.

vara,b:

array[0..100]oflongint;

  f1,f2:

array[0..10,0..52]oflongint;

  num:

array[0..51,0..51]oflongint;

  n,m,i,t,max1,min1,j,k:

longint;

functionmax(x,y:

longint):

longint;

begin

 ifx>ythenmax:

=xelsemax:

=y;

end;

functionmin(x,y:

longint):

longint;

begin

 ifx>ythenmin:

=yelsemin:

=x;

end;

begin

 readln(n,m);

 fori:

=1tondoreadln(a[i]);

 a[0]:

=a[n];

 max1:

=-maxlongint;

 min1:

=maxlongint;

 fort:

=1tondo

 begin

  fillchar(num,sizeof(num),0);

  fillchar(b,sizeof(b),0);

  fillchar(f1,sizeof(f1),0);

  fillchar(f2,sizeof(f2),0);

  fori:

=ttot+n-1dob[i-t+1]:

=a[imodn];

  fori:

=1tondo

   forj:

=itondonum[i,j]:

=(num[i,j-1]+((b[j]+10000000)mod10))mod10;

  fori:

=1tondo

  begin

   f1[1,i]:

=num[1,i];

   f2[1,i]:

=num[1,i];

  end;

  

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 初中教育 > 学科竞赛

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1