2010暑期兴趣班之贪心算法解题报告

2010暑期兴趣班之贪心算法-by helihui

A题:

总体思路,每次删除一个,删除一个后剩余数最小,每个阶段都是最优解。符合贪心原理。

题目给定要删除的位数n,那么删除的次数要执行n次。每次的策略是:从剩余数的头开始向后扫描,若出现递减,则把此位数删除。若到最后一位也没递减,说明此时数里每位都是非递减(此句废话o(_)o ),删除最后一位即可,这以后就简单了,若还有要删除的位

只要一直删除最后一位即可。

例子: 175438 4

第一步:扫描到7判断后面是5,有递减。将7删除剩余15438

第二步:从头再开始扫描,5后面4,有递减,删除5.剩余1438

第三步:。。。重复。删除剩余138的时候。再扫描无递减,当扫描到无递减以后直接删除最后一个即可,直到要删的删完结束。

最后剩余:13

 

B题:其实就是区间最多重叠数目。

箭头指向这块区域是重叠最多的三个会议时间冲突表示,那么需要开三个会场。

编程思路,先对开始时间升序排序,然后搜索统计,a数组记录会议。

Typedef struct

{

       Int s,e;

}Node;

Node a[1000];

for(i=0;i<n;i++)

{

sum=1;

       for(j=i-1;j>=0;j--)

       {

              if((a[i].s<a[j].e)) //开始时间已经升序排序了 那么如果冲突的话只要前一个会议的结束时间大于后一个会议的开始时间就说明冲突.

              {

                     sum+=1;

              }

       }

       if(sum>maxn)maxn=sum;

}

Maxn保存最大的重叠区间数(就是冲突的会议数 等于 需要的会场数)。

心得:此题建立正确的数学模型就能解决问题。

 

C: 搬桌子。仔细想想此题和B题很相似吧。可以说是一样的了。算出最大冲突的搬运次数。

如果不冲突,那么就是1*10. 如果n次冲突就是(n+1)*10

哪里会产生冲突,顾名思义就在那走廊,走廊比较短,只有200个格子分成。

我们用ans[200]数组记录走廊上产生的冲突.因为不冲突也有走的时间1*10.所以我们把走过就

看成冲突。在ans对应走过的ans[i]++

现在要搬的是3 -> 2  1 -> 6

首先转化下,观察发现,ans数组下标从1开始比较好。如果房间号是奇数

(上排那么对应的走廊下标 = 房间号/2+1),若房间号是偶数(下排,走廊下标 = 房间号/2

上图:ans记录情况就是 ans[1]=2; ans[2]=2;ans[3]=1;

按照那样统计,然后一个

For(i=1;i<=200;i++)

{

     If(ans[i]>max){max = ans[i];}

}

最后,max*10 就是最后结果了。很简单吧。

 

D题:这个题目比较简单。只要对(价值 / 时间)降序排序。这题要注意的是试卷是可以拆开来做,可分解的。贪心取价值比最大的一直就行,直至给定时间用完。

 

E题:和D题一个思路。对应交换比最多的降序排列。可以分解。一直取最大直至猫食交换完。

 

F题:为了叙述方便,把钓5分钟鱼称为钓一次鱼。首先枚举佳佳需要走过的琥珀数X,即假设他从湖泊1走到琥珀X,则路上花去时间T=T1+T2+T3+…+Tx-1。在这个前提下,可以认为佳佳能从一个湖“瞬间移动”到 另外 一个湖,即在任意一个时刻都可以从湖泊1到湖泊X任选一个钓一次鱼(想一想,为什么?)。

现在采取贪心策略,每次选一个鱼最多的湖泊钓一次鱼。对于每个湖泊来说,由于在任何时候鱼的数目只和佳佳在该湖里钓鱼的次数有关,和钓鱼总次数 无关,所以这个策略是最优的(请读者仔细想想)。假设一共允许钓K次鱼,那么每次在N个湖泊中选择鱼最多的一个钓,选择每次钓鱼地点的时间复杂度为On),故总的时间复杂度为O(kn^2)

---------此题分析摘自《算法艺术与信息学竞赛》。很好的分析了已经。

 

G题:此题乍一看,情况好多好烦。仔细想,采用贪心策略和题目所给的打包的大小的限制。

思路:最小打包数量,那么当然用6*6的来打包,然后下面三种给定的数据包肯定要新增打包数,6*65*54*4这三个包,然后知道足够多的2*21*1可以单独把6*6填满,而且5*54*4装包后剩余的空间可以用2*21*1完全填满。那么贪心策略就是,必须新开的包先开,然后计算剩余空间用1*1,和2*2填满。

Sum统计最小打包数量,a[]数组记录各种给定数据包数量,c[]用来记录1*12*2的包数量:

sum+=a[6]+a[5]+a[4];  //6*6 5*5 4*4 每个都需新开包

c[1]+=0*a[6]+11*a[5]; //6*6的空余为0,每个5*5的剩余111*1的。

c[2]+=a[4]*5;         //每个4*4的剩余2*25个。
//对于3*34个刚好填满一个6*6的包,故:
sum+=a[3]/4;

if(a[3]%4){sum++;c[1]+=8-(a[3]%4);c[2]+=7-2*(a[3]%4);} //计算剩余的空间。这句我压缩了看不出来。展开就是:

re = a[3]%4;
if ( re == 1 )// 以下都是再个中情况下 最多能余下的  1 * 1 2 * 2的格子 

{
     c[1] += 7;
     c[2] += 5;
}
else if ( re == 2 )
{
     c[1] += 6;
     c[2] += 3;
}
else if( re == 3 )
{
     c[1] += 5;
     c[2] += 1;
 }   

//自己可以算下 
 
还有要处理的就是给定要打包的2*2小于剩余的2*2的,那么就不需要新增加包。
若大于剩余2*2的空间个数,需要新增包。
给定1*1的需要打包的数量 小于 剩余1*1的空间个数(当然,如果上面2*2剩余空间多出来的
也可以放1*1的,所以得转化成1*1的个数),就不增加包数,否则需要另外增加。
(代码不多贴了)
 
 
 
H题:解决的不是很理想。等待看大家的方法。我简单说下我的方法。
就是回溯法(暴搜相当于)。
 
I题:此题意思是统计最少非递减序列(此序列里有满足:l<=l' and w<=w')的段数。
贪心,先排序下,升序排序,这里需要二重排序,对lw依次优先排序。
排序好以后,l肯定满足要求了,那么问题就相当于转化成求w数列的最少非递减序列段数。
编程思路,新开一个数组w用来保存该非递减数列的最大数。
依次扫描出a[i].w 然后在w数组的比较,更新w数组里的小于a[i].w的最大值。
如果没有小于 a[i].w的数存在那么sum++ (段数+1).并且将a[i].w放入
w数组(w[k++]=a[i].w)
以下代码就是模拟刚才的思路:事先已经经过二重升序排序了。
for(i=0;i<n;i++)
{
        mx=-1;
        for(j=k;j>=0;j--)
        {
               if((w[j]<=a[i].w)&&(mx<=w[j]))
               {
                       mx=w[j];wi=j;
               }
        }
        if(mx!=-1){w[wi]=a[i].w;}
        else
        {
               w[++k]=a[i].w;
               sum++;
        }
}
 
深入分析:细心的人会下那么如果a[i].w数列里是这个数列呢?
1 2 8 4呢 ? 不是1 2 4 8 更少吗?只有一段。当然不行,因为还有个a[i].l
已经对其排序,a[i].l在那种序列顺序下就转化成a[i].w数列的最小段数。
否则就不能转化成单a[i].w数列的最小段数了。
 
 
J题:没做。转xzc的,还是比较详细了。
这个挺神奇,现在看哪个代码,差点被自己雷到,搞了一个没有意义的函数

而且从头到脚没用过,无语啊,田忌赛马总知道怎么回事吧,这个题目么,
就是说每赢一次,赚钱200,输,扣200,平局就不算了,而我们变得程序起到的
就是孙膑的作用,尽量使田忌赚更多的钱,输入马的是速度,比赛方案由你支配
输出最多能赚多少钱就好了,每次输完一组马的速度就快排一次,然后比较,题目的
意思呢,T代表田忌,K代表皇帝,假如最快的T马比最快的K马快,那么两者相比,加钱
如果最慢的T马比最慢的K马快,两者相比,加钱,否则就T最慢的马就与K最快的马比赛,
扣钱,但是,可以保证多赛一场,
贪心策略:
如果 田忌最弱>大王最弱 田忌最弱PK大王最弱
如果 田忌最弱<大王最弱 田忌最弱PK大王最强
如果 田忌最弱=大王最弱

          如果田忌最强>大王最强 田忌最强PK大王最强

          如果田忌最强<大王最强 田忌最弱PK大王最强
          
如果田忌最强=大王最强 田忌最弱PK大王最强
下面是我循环里面的操作有点乱,
将就着看看――

if(a[i]>b[j])

{
        sum+=200;
}
else if(a[i]<b[j])
{
        sum-=200,i--,n--;
}
else
{
        while(a[n]>b[m]) 
        {sum+=200,n--,m--;}
        if(n==i) break; 
        else if(a[n]==a[i])
        {n--,i--;}
        else 
        {sum-=200,n--,i--;}
}  
 
 
K题:题目意思是给定了全部的牌数N*M,牌的大小也是依次1N*M
然后给你一副牌,别人当然不会拿到你有的牌了,剩余的大小的牌在其他人手里,
求出你最小的赢多少轮比赛,每轮比赛你的牌比任何其他人大才算赢得此轮比赛。
 
此题贪心思路容易想到。你出一张牌然后别人尽量用小的牌来压你。
编程思路:开个N*M大的ans数组,下标大小来表示牌大小。初始化为0
将自己的牌大小在该数组中标记为1.
  假设a数组保存了你的牌,那么:
for(i=0;i<n;i++)
{
        //循环比你大1递增牌
        for(j=a[i];j<=n*m;j++)
        {
               if(!ans[j])
               {//其他人此牌还没出
                       ans[j]=1;
                       
                       break;
               }
        }
        //若没有牌比你的大 那么你就赢此轮比赛 sum记录赢得比赛的轮数
        if(j>n*m){sum++;}
}

为解题报告打分
暂时不评分

★★
★★★
★★★★
★★★★★
发表您的评论(若贴AC代码或发表禁止言论等违禁行为将被删除并扣除积分)

|返回 |   | 转到页头|
Copyright @ 2008-2024(浙ICP备2022001332号), TZOJ. All Rights Reserved.