贪心例题集

这篇博客汇总了多个使用贪心算法解决的ACM竞赛题目,包括POJ3617、POJ3069和POJ3253等,通过详细描述题意、思路和AC代码,展示了贪心算法在不同问题中的应用策略。每个题目都从最小化成本或最优化覆盖的角度出发,提供了解题的贪心思路和实现代码。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

贪心


POJ3617贪心


POJ3617

描述

给定长度为N的字符串s,要构造一个长度为N的字符串T。期初,T是一个空串。
然后以下操作:

  • 从S的头部删除一个字符,加到T的尾部。

  • 从S的尾部删除一个字符,加到T的尾部。

目标是要使构造的字典序尽可能小。

思路

  • 我们很容易想到每次就看字符串S的头尾,把小的删除掉放到T的尾部就好。但是存在一个问题,当头尾的字符一样的时候,就必须要比较下一个字符的大小。
  • 所以我们就建立把S反过来为S2,S2与S比较,S小就把S的头字符删除;反之就把S2的头字符删除。

AC代码

    #include <iostream>
    #include <cstdio>
    #include <cstring>
    using namespace std;

    const int maxn = 2010;
    char s[maxn];
    char s2[maxn];
    char ans[maxn];
    int n;

    int strcmp(const char *src,const char *dst,int i,int j)
    {
        int flag = 0;
        for(; i < n && j < n ; i ++, j ++)
        {
            if(src[i] < dst[j])
                return 1;
            else if(src[i] > dst[j])
                return -1;
        }
        return 0;
    }

    int main()
    {
        cin >> n ;

        for(int i = 0; i < n; i ++)
        {
            cin >> s[i];
            s2[n-1-i] = s[i];
        }
        //cout<<s<<" "<<s2<<endl;

        //cout<<strcmp(s,s2,1,0)<<endl;

        //反过来比较
        int i,j,Index = 0;
        for(i = 0,j = 0; i < n && j < n && Index < n;Index ++)
        {
            if(strcmp(s,s2,i,j) > 0)
            {
                ans[Index] = s[i];
                i ++;
                //cout<<"a"<<endl;
            }

            else
            {
                ans[Index] = s2[j];
               // cout<<"b"<<endl;
                j ++;
            }
        }

        j = 1;
        for(int i = 0; i < n ;i ++)
        {
            if(j == 80)
            {
                cout<<ans[i]<<endl;
                j = 1;
            }

            else
            {
                cout<<ans[i];
                j ++;
            }

        }
        printf("\n");
        return 0;
    }

POJ3069_Saruman’s Army

http://poj.org/problem?id=3069

题意

  • 直线上有N个点,我们需要标记一些点。被标记的点有一个覆盖范围r。现在我们需要求最少的标记次数,并且使得每个点都被覆盖。(自己能覆盖自己)

思路

  • 我们从最左边考虑,设点为x0。要使得x0被覆盖,我们是想找到x0右边(包括x0)最远的一个能够覆盖到x0的点xi来标记,因为选xi左边的点没有任何好处,既然要覆盖x0,那就选最远的能够覆盖的点就是最好的选择。

  • 然后,按照这个思路我们就能往下找,找标记的点的右边第一个不能被覆盖的点作为x0,以此类推找下去就能得到最优解。

AC代码

    #include <iostream>
    #include <cstdio>
    #include <algorithm>
    using namespace std;

    const int maxn = 1010;
    int a[maxn];

    int main()
    {
        int r,n,ans;
        while(scanf("%d%d",&r,&n)!=EOF && !(r==-1 && n == -1))
        {
            for(int i = 0; i < n ;i ++)
                scanf("%d",&a[i]);

            sort(a,a+n);

            ans = 0;
            int i,j,z;
            for(i = 0; i < n ;)
            {
                for(j = i+1; j < n ;j ++)
                {
                    //距离过大 
                    if(a[j] - a[i] > r)
                    {
                        //j-1的位置标记 
                        ans ++; 

                        //寻找下一个没有被覆盖的点  
                        for(z = j; z < n ;z ++)
                        {
                            if(a[z] - a[j-1] > r)
                            {
                                i = z;
                                break;
                            }
                        }

                        //完全覆盖了 
                        if(z == n)
                            i = n;

                        break;
                    }
                }

                if(j == n)
                {
                    ans ++;
                    break;
                }

            }

            printf("%d\n",ans);
        }
        return 0;
    } 

POJ3253_Fence Repair

http://poj.org/problem?id=3253

题意

  • 现需将一块木板切成N-1块,每一块长度为Li。模板长度一定等于每一块的长度的和。每次切的时候的花费是切之前的木板长度,比如长为8,8和5,那么花费是第一次切成13和8,花费21,第二次把13切成8和5,花费13,总花费34。现在要求最少花费。

思路

  • 因为切的次数是一定的(n-1),我们想要花费少,就要尽可能地把大木板先切出来,也就是小木板放到后面切。
  • 但是正向我们切的时候不知道该怎么切,所以需要逆向思维,自底向上,由小木板组成大木板。对应于哈夫曼树,其实这个题就是要把小木板放到尽可能放到深度更深的点,大模板尽量放到深度浅的点。这样总的花费是最小的。
  • 逆向,假设已经切好了,需要拼接成一个大木板。我们每次选取最小的两个木板拼接,放入木板堆,然后再选两个最小的拼,以此类推。

AC代码


    #include <iostream>
    #include <cstdio>
    #include <algorithm> 
    #include <set>
    using namespace std;

    typedef long long LL;

    int main()
    {

        multiset<LL > s;
        multiset<LL >::iterator it1,it2;
        LL n,tmp;
        LL ans = 0;
        scanf("%lld",&n);
        for(int i = 0; i < n ;i ++)
        {
            scanf("%lld",&tmp);
            s.insert(tmp);      
        }

        it1 = s.begin();
        it2 = ++it1;
        it1--;
        while(s.size() > 1)
        {
    //      cout<<s.size()<<endl;
            tmp = *it1 + *it2;
            ans += tmp;
    //      cout<<*it1<<" "<<*it2<<endl;
            s.erase(it1);                //参数是迭代器 
            s.erase(it2);
            s.insert(tmp);
    //      cout<<s.size()<<endl;
            it1 = s.begin();
            it2 = ++it1;
            it1--;
        }

        printf("%lld",ans);

        return 0;
    }

VijosP1123均分纸牌

描述

有 N 堆纸牌,编号分别为 1,2,…, N。每堆上有若干张,但纸牌总数必为 N 的倍数。可以在任一堆上取若于张纸牌,然后移动。

移牌规则为:在编号为 1 堆上取的纸牌,只能移到编号为 2 的堆上;在编号为 N 的堆上取的纸牌,只能移到编号为 N-1 的堆上;其他堆上取的纸牌,可以移到相邻左边或右边的堆上。

现在要求找出一种移动方法,用最少的移动次数使每堆上纸牌数都一样多。

例如 N=4,4 堆纸牌数分别为:
① 9 ② 8 ③ 17 ④ 6
移动3次可达到目的:
从 ③ 取 4 张牌放到 ④ (9 8 13 10) -> 从 ③ 取 3 张牌放到 ②(9 11 10 10)-> 从 ② 取 1 张牌放到①(10 10 10 10)。
格式
输入格式

N(N 堆纸牌,1 <= N <= 100)
A1 A2 … An (N 堆纸牌,每堆纸牌初始数,l<= Ai <=10000)
输出格式

所有堆均达到相等时的最少移动次数。
样例1
样例输入1

4
9 8 17 6

样例输出1

3

思路

  • 首先题目说了纸牌总数是N的倍数,那么平均值一定是整数。
  • 然后,因为只能从相邻的纸牌堆中拿,而且要最少次数,也就是说不能左边给了右边,右边又给左边。
  • 所以我们就遍历一遍,每次如果数量就是平均值,继续;如果不是平均值,我们假设从逻辑上来讲一定从右边拿牌,也就是说,a[i+1] -= 平均值 - a[i],如果a[i]比平均值小,那么就是右边的牌减少,如果a[i]比平均值大,右边减去负数增大,也就是左边的牌传给了右边。
  • 这样每次就不用考虑左边的情况,因为左边那么给了你,要么你已经在上一步给了左边。

AC代码

    #include<iostream>
    #include<cstdio>
    using namespace std;

    int main()
    {
        int N;
        scanf("%d",&N);
        int a[110];
        int sum = 0;
        for(int i = 0; i < N ;i ++)
        {
            scanf("%d",&a[i]);
            sum += a[i];    
        } 

        //求平均值  因为是倍数 所以一定是整数 
        int junzhi = sum / N; 
        int ans = 0;

        for(int i = 0;i < N ;i ++)
        {
            if(a[i] == junzhi)
                continue;

            //因为只能从相邻的取 
            //都从右边取 ,从右边取负,表示传给右边 
            a[i+1] -= junzhi - a[i];
            ans ++;
        }

        printf("%d\n",ans);

        return 0;
    }

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值