2.2 贪心算法(二)(《挑战程序设计竞赛》)

书接上一回,继续做题:


字典序最小问题:Best Cow Line ( POJ 3617 )
这里写图片描述
这里写图片描述
最开始的想法是:对比首尾的字符大小,选小的放入T中
而当首尾字符相同的时候,对比第二个和倒数第二个的字符,选小的放入T中,如果还相同,则对比第三个和倒数第三个字符,一次类推。如果都相等的话,则是回文串,任意选两边都可以。

#include<iostream>
#include<stdio.h>
using namespace std;
int main()
{
    int n,i,a,b;
    char s[2010];
    while(~scanf("%d",&n))
    {
        scanf("%s",s);
        a=0,b=n-1;
        while(a<=b) //包含a==b是为了输出最后一个
        {
            bool left=false;
            for(i=0;a+i<=b;i++)
            {
                if(s[a+i]<s[b-i])
                {
                    left=true;
                    break;
                }
                else
                {
                    if(s[a+i]>s[b-i])
                    {
                        left=false;
                        break;
                    }
                }
            }
            if(left) printf("%c",s[a++]);
            else printf("%c",s[b--]);
        }
        printf("\n");
    }
}

对于奇数,如:ABCBA
最后比较的是C和C,相等,放入默认的一边
对于偶数,如:ABCCBA
最后比较的还是C和C,也是相等的,放入默认的一边


Saruman 's Army ( POJ 30669 )
这里写图片描述

#include<iostream>
#include<stdio.h>
#include<algorithm>
using namespace std;
int n,r,x[1005];
int main()
{
    scanf("%d%d",&n,&r);
    int i,ans=0;
    for(i=0;i<n;i++) scanf("%d",&x[i]);
    sort(x,x+n);
    i=0;
    while(i<n)
    {
        int s=x[i++]; //s为没有被覆盖的最左的点的位置
        while(i<n&&x[i]<=s+r) i++;
        int p=x[i-1]; //p是新加上标记的点的位置
        while(i<n&&x[i]<=p+r) i++;
        ans++;
    }
    printf("%d\n",ans);
}

书上给出的代码是要记录两个点,一个是开始的点,s,另一个是标记的点,p
考虑到两个while写的内容一样,能否只用一个标记点呢?毕竟只是找每个点的下一个合适的点,把新点的数据覆盖旧点就可以了,这样一个s就能重复用。
我尝试只用一个点,从12行开始改写:

    i=0;
    s=x[i++];
    while(i<n)
    {
        while(i<n&&x[i]<=s+r) i++;
        //判断退出条件 两个判断条件不能同时等价
        if(i==n) break;//说明x[i]走到尽头,一直都<=s+r
        else
        {   //x[i]>s+r
            s=x[i++]; //新的起点
            ans++;
        }
    }

但输入
R=0 N=3 x={ 10 , 20 , 20 } 后
我的代码输出的是1,而正确答案是2
原因是每找到一个新的起点,ans才增一。而10 20 20 这一组,新的起点只有第一个20,想继续找下一个新起点时,i已经等于n,找不到第二个新起点。所以ans只等于1。

回看书上的代码,找到p=10为标记点后ans加一,又找到20为标记点,ans又加一,所以ans=2。另外,p=20之后,用while一直寻找距离20超过0的点,直到走到尽头也没有找到,程序结束。


这题涉及一点二叉树的知识:
Fence Repair ( POJ 3253 )
这里写图片描述
这里写图片描述
思路是:
如果两块板是最短和次短的,那么他们的消耗是最少的,记录下这两块板的总长,把这两块板拼合当做一块板后,再在剩下的n-1块板中找到最短和次短的,记录下这两块板的总长,把最短和次短拼合成另一块板,再在剩下n-2块板中寻找。直到最后只剩一块板。
可以尝试别的切割方法来感受一下。

代码如下:

#include<iostream>
#include<stdio.h>
#include<algorithm>
using namespace std;
int main()
{
    int i,n,ans=0,t,L[50005],min1,min2;
    scanf("%d",&n);
    for(i=0;i<n;i++) scanf("%d",&L[i]);
    while(n>1)
    {   //min1为最小值 min2为次小值
        min1=0;min2=1;
        if(L[min1]>L[min2]) swap(min1,min2);
        //找出最小值和次小值
        for(i=2;i<n;i++)
        {
            if(L[i]<L[min1]) //若当前值比已知最小值还要小
            {
                min2=min1;//现在的次小值的下标就是旧的最小值的下标
                min1=i;//更新最小值的小标
            }
            else if(L[i]<L[min2]) //L[i]>=L[min2]&&L[i]<L[min2]
                    min2=i; //更新次小值的下标
        }
        //现在知道最小值和次小值的下标
        t=L[min1]+L[min2]; //t为两块最小和次小木板的长度和
        ans+=t; //统计答案
        //更新L数组
        if(min1==n-1) swap(min1,min2);
        //因为L[min1]和L[min2]这两块木板已经合并,所以这两个数组要更新
        L[min1]=t; //L[min1]数组用来记录合成的木板长度,形成新的木板
        L[min2]=L[n-1]; //L[min2]更改成L[n-1]
        n--; //总木板数量减少
    }
}

对于代码:if(min1n-1) swap(min1,min2);
是防止min1等于n-1时,在n–后,L[n-1]的值没有保存下来
例如:L={3,4,5,2,1} 时
在第一轮:n=5,min1=4,min2=3,没有这一句的话,接下来:
L[4]=t;
L[3]=L[5-1]; //即L[3]=L[4]=t;
然后n–; // n=4;
L[4] 在接下来就不会搜到,且L[n-1]的值没有保存下来,从而引发错误
加了句之后,如果min2
n-1,接下来L[min2]=L[n-1],即自己等于自己,本来下一轮也不会被搜索到,仍不受影响
综上:
被合并的两块木板的空间,一块存放新合并木板的值,另一块存放下一轮即将搜索不到的值

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值