acm备考资料整理by liangge.docx
《acm备考资料整理by liangge.docx》由会员分享,可在线阅读,更多相关《acm备考资料整理by liangge.docx(118页珍藏版)》请在冰豆网上搜索。
![acm备考资料整理by liangge.docx](https://file1.bdocx.com/fileroot1/2023-2/23/2cac6808-7eab-44af-b5da-53c64b55b7b8/2cac6808-7eab-44af-b5da-53c64b55b7b81.gif)
acm备考资料整理byliangge
背包问题
P01:
01背包问题
题目
有N件物品和一个容量为V的背包。
第i件物品的费用是c[i],价值是w[i]。
求解将哪些物品装入背包可使价值总和最大。
基本思路
这是最基础的背包问题,特点是:
每种物品仅有一件,可以选择放或不放。
用子问题定义状态:
即f[i][v]表示前i件物品恰放入一个容量为v的背包可以获得的最大价值。
则其状态转移方程便是:
f[i][v]=max{f[i-1][v],f[i-1][v-c[i]]+w[i]}
先考虑上面讲的基本思路如何实现,肯定是有一个主循环i=1..N,每次算出来二维数组f[i][0..V]的所有值。
那么,如果只用一个数组f[0..V],能不能保证第i次循环结束后f[v]中表示的就是我们定义的状态f[i][v]呢?
f[i][v]是由f[i-1][v]和f[i-1][v-c[i]]两个子问题递推而来,能否保证在推f[i][v]时(也即在第i次主循环中推f[v]时)能够得到f[i-1][v]和f[i-1][v-c[i]]的值呢?
事实上,这要求在每次主循环中我们以v=V..0的顺序推f[v],这样才能保证推f[v]时f[v-c[i]]保存的是状态f[i-1][v-c[i]]的值。
伪代码如下:
fori=1..N
forv=V..0
f[v]=max{f[v],f[v-c[i]]+w[i]};
其中的f[v]=max{f[v],f[v-c[i]]}一句恰就相当于我们的转移方程f[i][v]=max{f[i-1][v],f[i-1][v-c[i]]},因为现在的f[v-c[i]]就相当于原来的f[i-1][v-c[i]]。
如果将v的循环顺序从上面的逆序改成顺序的话,那么则成了f[i][v]由f[i][v-c[i]]推知,与本题意不符,但它却是另一个重要的背包问题P02最简捷的解决方案,故学习只用一维数组解01背包问题是十分必要的。
事实上,使用一维数组解01背包的程序在后面会被多次用到,所以这里抽象出一个处理一件01背包中的物品过程,以后的代码中直接调用不加说明。
过程ZeroOnePack,表示处理一件01背包中的物品,两个参数cost、weight分别表明这件物品的费用和价值。
procedureZeroOnePack(cost,weight)
forv=V..cost
f[v]=max{f[v],f[v-cost]+weight}
初始化的细节问题
我们看到的求最优解的背包问题题目中,事实上有两种不太相同的问法。
有的题目要求“恰好装满背包”时的最优解,有的题目则并没有要求必须把背包装满。
一种区别这两种问法的实现方法是在初始化的时候有所不同。
如果是第一种问法,要求恰好装满背包,那么在初始化时除了f[0]为0其它f[1..V]均设为-∞,这样就可以保证最终得到的f[N]是一种恰好装满背包的最优解。
如果并没有要求必须把背包装满,而是只希望价格尽量大,初始化时应该将f[0..V]全部设为0。
为什么呢?
可以这样理解:
初始化的f数组事实上就是在没有任何物品可以放入背包时的合法状态。
如果要求背包恰好装满,那么此时只有容量为0的背包可能被价值为0的nothing“恰好装满”,其它容量的背包均没有合法的解,属于未定义的状态,它们的值就都应该是-∞了。
如果背包并非必须被装满,那么任何容量的背包都有一个合法解“什么都不装”,这个解的价值为0,所以初始时状态的值也就全部为0了。
这个小技巧完全可以推广到其它类型的背包问题,后面也就不再对进行状态转移之前的初始化进行讲解。
一个常数优化
前面的伪代码中有forv=V..1,可以将这个循环的下限进行改进。
由于只需要最后f[v]的值,倒推前一个物品,其实只要知道f[v-w[n]]即可。
以此类推,对以第j个背包,其实只需要知道到f[v-sum{w[j..n]}]即可,即代码中的
fori=1..N
forv=V..0
可以改成
fori=1..n
bound=max{V-sum{w[i..n]},c[i]}
forv=V..bound
这对于V比较大时是有用的。
输入格式
输入数据首先包含一个正整数C,表示有C组测试用例,每组测试用例的第一行是两个整数n和m(1<=n<=100,1<=m<=100),分别表示经费的金额和大米的种类,然后是m行数据,每行包含3个数p,h和c(1<=p<=20,1<=h<=200,1<=c<=20),分别表示每袋的价格、每袋的重量以及对应种类大米的袋数。
输出格式
对于每组测试数据,请输出能够购买大米的最多重量,你可以假设经费买不光所有的大米,并且经费你可以不用完。
每个实例的输出占一行。
输入样例
1
82
21004
41002
输出样例
400
#include
#include
usingnamespacestd;
#defineN1005
#defineMax(a,b)((a)>(b)?
(a):
(b))
#defineMin(a,b)((a)<(b)?
(a):
(b))
intdp[N][N];
intmain()
{
intt;
inti,j,k,am,a;
intn,m;
intmax;
intvol[N],val[N],nn[N];
cin>>t;
while(t--)
{
cin>>m>>n;
for(i=1;i<=n;i++)
cin>>vol[i]>>val[i]>>nn[i];
memset(dp,0,sizeof(dp));
for(i=1;i<=n;i++)
for(j=m;j>=0;j--)
{
max=-1;
for(k=0;k<=nn[i]&&k*vol[i]<=j;k++)
max=Max(max,dp[i-1][j-k*vol[i]]+k*val[i]);
dp[i][j]=max;
}
cout<}
return0;
}
下面为0-1背包问题,上面为多重背包
#include
usingnamespacestd;
#defineMAXSIZE1000
intf[MAXSIZE+1],c[MAXSIZE+1],w[MAXSIZE+1];
intmain()
{
intN,V;
cin>>V>>N;
inti=1;
for(;i<=N;++i)
{
cin>>c[i]>>w[i];
}
for(i=1;i<=N;++i)
{
for(intv=V;v>=c[i];--v)//c[i]可优化为bound,bound=max{V-sumc[i,...n],c[i]}
{
f[v]=(f[v]>f[v-c[i]]+w[i]?
f[v]:
f[v-c[i]]+w[i]);
}
}
//当i=N时,可以跳出循环单独计算F[V]
cout<return0;
}
背包问题,贪心法
#include
#include
#include
usingnamespacestd;
inti,j,n;
floatW,x[1000],sum;
structrm
{
floatw;
floatv;
}room[1000];
boolcmp(rma,rmb)
{
returna.v/a.w>b.v/b.w;
}
intmain()
{
cin>>W>>n;
while(n!
=-1&&W!
=-1)
{
for(i=0;icin>>room[i].v>>room[i].w;
sort(room,room+n,cmp);
memset(x,0,1000);
i=0;
while(room[i].w{
x[i]=room[i].v;
W=W-room[i].w;
i++;
}
x[i]=room[i].v/room[i].w*W;
j=0;sum=0.0;
while(j<=i)
{
sum+=x[j];
j++;
}
cout<:
fixed)<cin>>W>>n;
}
return0;
}
输入样例
53
72
43
52
-1-1(结束)
输出样例
13.333对v/w从大到小排列,再依次选择
八皇后问题
#include
#include
#include
usingnamespacestd;
structnode1
{boolb[8][8];};
structnode2
{intx,y;};
node1visited[9];
node2zb[8];
intnum;
voidprint()
{printf("case%d:
",++num);
for(inti=0;i<=7;i++)
{printf("%d,%d\t",zb[i].x,zb[i].y);}
cout<<'\n';
}
intx1,y1,x4,y4;
voidvis(intx,inty,intstep){
x1=x;y1=y;
x4=x;y4=y;
visited[step]=visited[step-1];
for(inti=0;i<8;i++)
{
visited[step].b[x][i]=0;
visited[step].b[i][y]=0;
}
while(x1<8&&y1<8)
{
visited[step].b[x1][y1]=0;
x1++;y1++;
}
while(x4<8&&y4>=0)
{
visited[step].b[x4][y4]=0;
x4++;y4--;
}
}
voidDFS(intstep)
{
if(step==9)
print();
else
{
for(intj=0;j<8;j++)
if(visited[step-1].b[step-1][j])
{
zb[step-1].x=step-1;
zb[step-1].y=j;
vis(step-1,j,step);
DFS(step+1);
}}}
intmain(){
num=0;
memset(visited,1,sizeof(visited));
DFS
(1);
//cout<<"helloworld"<system("pause");
}
站位问题:
#include
#include
usingnamespacestd;
intcounter=0;
voiddisplay(intplace[])
{
for(inti=1;i<=6;i++)
{
cout<if(i%3==0)
cout<<"\n";
}
cout<}
booljudge(intmark,intplace[])
{
switch(mark)
{
case1:
returntrue;
case2:
returnplace[2]!
=5&&place[2]!
=6;
case3:
returnplace[3]!
=2&&place[3]!
=3;
case4:
returnplace[4]!
=1&&place[4]!
=2&&place[4]!
=3&&place[4]!
=6;
case5:
returnplace[5]!
=1&&place[5]!
=5&&place[5]!
=6;
case6:
returnplace[6]!
=1&&place[6]!
=6;
}
}
boolothers(intplace[])
{
intm=1,n=1;
for(inti=0;i<=6;i++)
{
if(place[i]==3)
m=i-1;
if(place[i]==4)
n=i-1;
}
returnm/3!
=n/3;
}
voidbacktrace(intmark,intplace[],intpeople[])
{
for(inti=1;i<=6;i++)
{
if(people[i]!
=0)
{
place[mark]=people[i];
if(judge(mark,place))
{
if(mark==6&&others(place))
{
counter++;
display(place);
}
else
{
people[i]=0;
backtrace(mark+1,place,people);
people[i]=i;
}
}
}
}
}
intmain(intargc,char*argv[])
{
int*place=newint[7];
int*people=newint[7];
for(inti=1;i<=6;i++)
people[i]=i;
backtrace(1,place,people);
cout<<"Thetotalmethodsis"<system("PAUSE");
returnEXIT_SUCCESS;
}
把一个数分解为若干数的和
#include
#include
#defineMAXSIZE50
staticintbuff[MAXSIZE];
intSum(intbuff[],inti)
{
intsum=0;
for(intm=0;m<=i;m++)
sum+=buff[m];
returnsum;
}
voidprintFactor(intbuff[],inti,constintnumber)
{
intcount=0,m;
for(m=0;m<=i;m++)
{
printf("%4d",buff[m]);
}
if(number==buff[0])
printf("%4d",0);
printf("\n");
}
voiddivide(inti,inttop,intnumber)
{
intj;
for(j=i;j>0;j--)
{
buff[top]=j;
if(number==Sum(buff,top))
printFactor(buff,top,number);
elseif(numbercontinue;
if(top>=number)
continue;
divide(j,top+1,number);
}
return;
}
voidmain()
{
inti,top=0;
intnumber;
printf("inputasoucenumber:
");
scanf("%d",&number);
i=number;
divide(i,top,number);
}
普里姆prim最小生成数
#include
#include
#defineN100
intp[N],key[N],tb[N][N];//key[j]表示到达j点的路径
voidprim(intv,intn){
inti,j;
intmin;
for(i=1;i<=n;i++){//从第一个顶点开始找
p[i]=v;
key[i]=tb[v][i];//初始化到达i点的各个值
}
key[v]=0;
for(i=2;i<=n;i++){
min=INT_MAX;
for(j=1;j<=n;j++)
if(key[j]>0&&key[j]v=j;//所以条件key[j]>0保证访问不会构成环
min=key[j];}
printf("%d%d",p[v],v);
key[v]=0;//访问过的点被置为0
for(j=1;j<=n;j++)
if(tb[v][j]p[j]=v,key[j]=tb[v][j];
}
}
intmain()
{
intn,m;
inti,j;
intu,v,w;
while(scanf("%d%d",&n,&m))//输入顶点数,边数和某点到某点的权值
{
for(i=1;i<=n;i++)
{
for(j=1;j<=n;j++)
tb[i][j]=INT_MAX;
}
while(m--){
scanf("%d%d%d",&u,&v,&w);
tb[u][v]=tb[v][u]=w;
}
prim(1,n);
printf("\n");
}
return0;
}
Kruskal算法N城市遍历问题(依次选取最小边组成生成数)
#include
#include
#include
#definemax20
#defineMAX_LNT10
typedefstructnode/*构造一个结构体,两个城市可以看成起点和终点,之间的道路可以看成一个边*/
{
intstr;/*起点*/
intend;/*终点*/
intdis;/*距离*/
}node;
nodep[max],temp;/*p记录城市信息*/
intpre[100],rank[100];/*用于判断是否构成回路*/
intn=0,arcs[MAX_LNT][MAX_LNT];/*n表示城市个数,arcs[][]记录城市间权值*/
intmenu()/*菜单函数*/
{
intm;
printf("..........................2010年7月29日......................\n\n");
printf("求最小生成树\n");
printf("________________________________\n\n");
printf("1输入城市之间的信息\n");
printf("2判断是否能构成一个最小生成树\n");
printf("3遍历所有城市生成最小生成树\n");
printf("4退出\n");
printf("________________________________\n\n");
printf("请输入所选功能1-4\n");
system("colorE");/*改变界面颜色的,对程序没什么影响*/
scanf("%d",&m);
returnm;
}
/*下面三个函数作用是检验当一条边添加进去,是否会产生回路*/
voidset(intx)/*初始化*/11
{
pre[x]=x;
rank[x]=0;
}
intfind(intx)/*找到这个点的祖先*/
{
if(x!
=pre[x])
pre[x]=find(pre[x]);
returnpre[x];
}
voidUnion(intx,inty)/*将这两个添加到一个集合里去*/
{
x=find(x);
y=find(y);
if(rank[x]>=rank[y])
{
pre[y]=x;
rank[x]++;
}
elsepre[y]=x;
}
voidKruskal()
{
intans=0,i,j,k=0;/*ans用来记录生成最小树的权总值*/
intindex;
intcount=0;/*记录打印边的条数*/
for(i=1;i<=n;i++)/*初始化数组pre[x],rank[x]*/
set(i);
for(i=1;i<=n;i++)
{
for(j=i+1;j<=n;j++)
{
p[++k].str=i;
p[k].end=j;
p[k].dis=arcs[i][j];/*先把所有城市之间的路段看成一个边*/
}
}
for(i=1;i<=k;i++)/*把所有的边按从小到大进行排序*/
{
index=i;
for(j=i+1;j<=k;j++)if(p[j].dis
temp=p[index];
p[index]=p[i];
p[i]=temp;
}
for(i=1;i<=k;i++)
{
if(find(p[i].str)!
=find(p[i].end))/*如果这两点连接在一起不构成一个回路,
则执行下面操作*/
{
printf("\t第%d条路段为:
%d--%d,权值为%d\n",++
count,p[i].str,p[i].end,p[i].dis);/*将这条边的起点、终点打印出来*/
ans+=p[i].dis;/*说明这条路段要用*/
Union(p[i].str,p[i].end);
}
}
printf("\t遍历所有城市得到最小生成树的代价为:
%d\n\n",ans);
}
voidcreate()/*输入城市信息*/
{
inti,j;
printf("请输入城市的个数:
\n