这周继续深刻的学习了贪心算法,动态规划也开了个头,但给我印象最深的还是周四那场比赛。可以说是让我再一次理解什么才是ACM的真谛。
一、贪心
1、 最优装载问题
例如贪心作业M
题目大意:工厂生产以相同高度 h 和大小 1×1、2×2、3×3、4×4、5×5、6×6 的方包包装的产品。这些产品始终以与产品相同高度和大小 6*6 的方形地块交付给客户。由于费用问题,工厂和客户的利益是尽量减少从工厂向客户交付订购产品所需的包裹数量。一个好的程序解决了根据订单找到交付给定产品所需的最少数量包裹的问题,这将节省大量资金。
思路分析:每个4×4、5×5、6×6的货物都需要单独开辟一个箱子。3×3的箱子也需要单独开辟,但它只需要每四个货物开辟一个箱子,需要注意的是,如果有剩余那么要单独再开辟一个箱子。算完箱子个数后,我们便要查剩余空间了,首先我们要用剩余的空间先去装2×2的货物,所以这时我们就要判断剩余的2×2的空间是否能完全装完2×2的货物,来决定是否要为2×2的货物单独开辟空间。最后判断1×1的空间是否能装完1×1的货物,判断方法同2×2类似。
#include <iostream>
#include <algorithm>
using namespace std;
int main()
{
int goods1,goods2,goods3,goods4,goods5,goods6;
while(cin>>goods1>>goods2>>goods3>>goods4>>goods5>>goods6)
{
int sum=0;
int one=0,two=0;
if(goods1==0&&goods2==0&&goods3==0&&goods4==0&&goods5==0&&goods6==0)break;
sum+=goods6+goods5+goods4+(goods3+3)/4;
if(goods3%4==1)
{
two+=5;
one+=7;
}
if(goods3%4==2)
{
two+=3;
one+=6;
}
if(goods3%4==3)
{
two+=1;
one+=5;
}
if(goods4!=0)two+=goods4*5;
if(goods5!=0)one+=goods5*11;
if(two<goods2)sum+=(goods2-two+8)/9;
one=36*sum-36*goods6-25*goods5-16*goods4-9*goods3-4*goods2;
if(one<goods1)sum+=(goods1-one+35)/36;
cout<<sum<<endl;
}
return 0;
}
首先,这里有一个小技巧
sum+=goods6+goods5+goods4+(goods3+3)/4;
这里的(goods3+3)/4就是算3×3的货物需要几个箱子。
这种方式在很多有剩余题目算个数时会经常用到。
2、贪心作业v,这个题虽然要用贪心的思想,但令我感触最深的还是一个标记当前位置的思想。
由于题目叙述过于繁琐,这里简单说一下题目是要我们干嘛。
20 33 25 32 99
32 86 99 25 10
20 99 10 33 86
19 33 74 99 32
这里有一组数据,题目要求就是让我们找出出现次数第二多的数,如果不止一个,那便按升序输出,这里输出32、33。
我的思路(较为繁琐):将所有数据输入,给他们排序,找出一样的数据(肯定相连),记录每组一样数据的第一个数据的位置,他后面相连一样数据的位置赋值为0。然后用循环找出最多的数据,赋值为0;再找一次最多的数据,这个数据便是我们要找的出现次数第二多的数据(这里用了四次循环,繁琐)。最后再输出该数据。
#include <iostream>
#include <algorithm>
#include <cmath>
#include <string.h>
using namespace std;
int main()
{
int n,m;
int count;
int p[1000];//记录找到后的数据
int y[250000];//记录数据出现次数,找第二多
int x[250001];//记录原输入数据
while(cin>>n>>m)
{
if(n==0&&m==0)break;
int ans=0;
memset(y,1,sizeof(y));
for(int a=0;a<n*m;a++)//输入原始数据
cin>>x[a];
sort(x,x+n*m);//排序
int t=-1;
for(int a=0;a<n*m;a++)//判断每个数据的出现次数,y的下标记录原始数据的位置
if(t!=x[a])
{
t=x[a];
count=a;
}
else {y[count]++;y[a]=0;}
int max=0;
for(int a=0;a<n*m;a++)//删除出现次数最多的数据
if(max<y[a])max=y[a];
for(int a=0;a<n*m;a++)
if(max==y[a])y[a]=0;
int max1=0;
for(int a=0;a<n*m;a++)//找出出现次数第二多的数据
if(max1<y[a])max1=y[a];
for(int a=0;a<n*m;a++)//记录出在原始数据中的下标
if(max1==y[a]){p[ans]=a;ans++;}
sort(p,p+ans);//升序
for(int a=0;a<ans;a++)//输出
cout<<x[p[a]]<<" ";
cout<<endl;
}
return 0;
}
下面是一组题解代码
#include <iostream>
using namespace std;
int player[100001];
int main()
{
int N,M;
while (cin>>N>>M)
{
memset(player,0,sizeof(player));
int number,max=0,j,t,flag=0;
if (N==0&&M==0)
break;
for (int i=0;i<N*M;i++) // 求出出现次数最多的编号
{
cin>>number;
player[number]++;
if (max<player[number])
max=player[number];
}
for (j=max-1;j>=1;j--) // 根据出现次数最多的编号,依次减一,找第二多的。
{
for (t=1;t<=10000;t++)
{
if (player[t]==j) //找到次数第二多的。输出。
{
flag=1;
cout<<t;
}
if (flag==1)
break;
}
if (flag==1)
break;
}
if (flag==1) //输出编号。
for (t=t+1;t<=10000;t++)
if (player[t]==j)
cout<<" "<<t;
cout<<endl;
}
return 0;
}
相比之下,我的思路不仅繁琐,而且很难考虑,即便让我自己去看一遍代码,我都很难看懂。题解便很容易去理解,而且思路清晰,将该记录的位置标记上,而我虽然也注重了标记,但标记的内容太难去理解,而且标记的次数过多,很难去理解。
二、codeforces比赛及感悟
先列举比赛中的第一题来体会ACM的思维性。
1 4 7 10 13 以列排
2 5 8 11 14
3 6 9 12 15 取出11,找出11在以行排中位置的数,即9。
1 2 3 4 5
6 7 8 9 10
11 12 13 14 15以行排
这道题看似时一道二位数组题,但是在做题的时候完全用不到二维数组,只需要找出11在以列排中处于第几行,第几列就行。
这次比赛更为明确的阐释ACM注重思路,只要思路正确,看似很繁杂的题目只需要几行代码便可以解决。
这次比赛,让我感受到自己的思维量不足,做题时思路不够清晰,见过的题目也太少,这边需要多去看一些思维题。寒假自己虽然也跟着训练了几天,但当时做题时没有认识到ACM的思维性,只是一味的抱着出题就行的心态,仍认为ACM和我们平常学的差不多,只是知识面广罢了。再就是看完别人的代码,发现自己STL容器还是掌握程度不够,很多代码都看不懂,这便需要去深刻学习,真正去弄明白,避免模棱两可的学东西。