贪心算法题型总结

挑选了几个不同的关于贪心算法的题,有按C写的也有后来补充的C++写的。

题1 POJ – 2940

题目描述

Wine Trading in Gergovia

As you may know from the comic “Asterix and the Chieftain’s Shield”, Gergovia consists of one street, and every inhabitant of the city is a wine salesman. You wonder how this economy works? Simple enough: everyone buys wine from other inhabitants of the city. Every day each inhabitant decides how much wine he wants to buy or sell. Interestingly, demand and supply is always the same, so that each inhabitant gets what he wants.

There is one problem, however: Transporting wine from one house to another results in work. Since all wines are equally good, the inhabitants of Gergovia don’t care which persons they are doing trade with, they are only interested in selling or buying a specific amount of wine. They are clever enough to figure out a way of trading so that the overall amount of work needed for transports is minimized.

In this problem you are asked to reconstruct the trading during one day in Gergovia. For simplicity we will assume that the houses are built along a straight line with equal distance between adjacent houses. Transporting one bottle of wine from one house to an adjacent house results in one unit of work.

Input

The input consists of several test cases.

Each test case starts with the number of inhabitants n (2 ≤ n ≤ 100000). The following line contains n integers ai (−1000 ≤ ai ≤ 1000). If ai ≥ 0, it means that the inhabitant living in the ith house wants to buy ai bottles of wine, otherwise if ai < 0, he wants to sell −ai bottles of wine. You may assume that the numbers ai sum up to 0.

The last test case is followed by a line containing 0.

Output

For each test case print the minimum amount of work units needed so that every inhabitant has his demand fulfilled. You may assume that this number fits into a signed 64-bit integer (in C/C++ you can use the data type “long long” or “__int64”, in JAVA the data type “long”).

Sample Input:

5
5 -4 1 -3 1
6
-1000 -1000 -1000 1000 1000 1000
0

Sample Output:

9
9000

解题思路

1.路费与路程成正比,那么路程越近越好;我们来假设前3户人家A家, B家, C家分别为 +3, +2 , -1 ;那么A直接给C , 与A先寄存给B(假设寄存一样要路费),再给C,路费分别为 1(酒数量) * 2 (路程) = 2和 1 * ( 1 + 1) = 2,那么如果买家要找一个最近的卖家,那我们无需考虑究竟这个卖家在哪里,只需要一直寄存过去,直到买卖数量抵消。
2.问题那么就简化为,相当于无论是买酒或是卖酒,只需与相邻的人买卖,不够可以欠着;
如,A需要买5(+5),A右边一家B需要卖3(-3),B不够,可以欠着,变为+3;以此类推。
3.每次路程为1,买卖酒数量为当前这一家的数量。
4.在递推酒数量的过程中,相当于已经递推了路程。

代码解析


#include <stdio.h>
#include <stdlib.h>
#include <math.h>
int main()
{
    int n,i;
    int a[100000] = {0};

    while (scanf("%d",&n) != EOF && n != 0) {
        long long int sum = 0;
        for (i = 0; i < n; i++)
            scanf("%d",&a[i]);

        for (i = 0; i < n; i++) {          
            a[i+1] += a[i];
            sum += (long long int)(abs(a[i]));       
        }
        printf("%lld\n",sum);
    }
    return 0;
}

题2 HDU – 1009

题目描述

FatMouse’ Trade

FatMouse prepared M pounds of cat food, ready to trade with the cats guarding the warehouse containing his favorite food, JavaBean.
The warehouse has N rooms. The i-th room contains J[i] pounds of JavaBeans and requires F[i] pounds of cat food. FatMouse does not have to trade for all the JavaBeans in the room, instead, he may get J[i]* a% pounds of JavaBeans if he pays F[i]* a% pounds of cat food. Here a is a real number. Now he is assigning this homework to you: tell him the maximum amount of JavaBeans he can obtain.

Input
The input consists of multiple test cases. Each test case begins with a line containing two non-negative integers M and N. Then N lines follow, each contains two non-negative integers J[i] and F[i] respectively. The last test case is followed by two -1’s. All integers are not greater than 1000.

Output
For each test case, print in a single line a real number accurate up to 3 decimal places, which is the maximum amount of JavaBeans that FatMouse can obtain.

Sample Input
5 3
7 2
4 3
5 2
20 3
25 18
24 15
15 10
-1 -1

Sample Output
13.333
31.500

解题思路

按 J[i]/F[i] 的比值由大到小排序,每个房间能获得的JavaBean相加,当最后一个房间不够时,按比例获得。

代码解析

#include <stdio.h>
struct a
{
    double j;
    double f;
    double k;

};

int main()
{
    int m,n,i,j;
    struct a tmp;
    struct a tr[10000];

    while (scanf("%d %d",&m,&n) != EOF && m != -1 && n != -1) {
        for (i = 0; i < n; i++) {     
            scanf("%lf %lf",&tr[i].j,&tr[i].f);
            tr[i].k = tr[i].j/tr[i].f;
        }

        for (i = 0; i < (n-1); i++) {
            for (j = 0; j < (n-1-i); j++) {
                if (tr[j].k < tr[j+1].k) {                    
                    tmp = tr[j];
                    tr[j] = tr[j+1];
                    tr[j+1] = tmp;                    
                }
            }
        }
        double sum = 0;        
        for (i = 0; i < n; i++) {                        
            if ( (m - tr[i].f) > 0) {                
                sum += tr[i].j;
                m -= tr[i].f;                
            } else{                
                sum += ((double)m/tr[i].f)*tr[i].j;
                break;
            }                        
        }
        printf("%0.3f\n",sum);        
    }
    return 0;
}

题3 HDU – 2089

题目描述

不要62

杭州人称那些傻乎乎粘嗒嗒的人为62(音:laoer)。
杭州交通管理局经常会扩充一些的士车牌照,新近出来一个好消息,以后上牌照,不再含有不吉利的数字了,这样一来,就可以消除个别的士司机和乘客的心理障碍,更安全地服务大众。
不吉利的数字为所有含有4或62的号码。例如:
62315 73418 88914
都属于不吉利号码。但是,61152虽然含有6和2,但不是62连号,所以不属于不吉利数字之列。
你的任务是,对于每次给出的一个牌照区间号,推断出交管局今次又要实际上给多少辆新的士车上牌照了。

Input
输入的都是整数对n、m(0

解题思路

如果直接遍历过去,那么时间很可能会超,这时候我们可以用到一个小技巧:“预处理”;
什么意思呢,比如说这道题,整数对在0到1000000之间,那我们可以将0到1000000的数先判断好,到底是否含不吉利的数,判断完以后,当我们输入数字时,无需再一个个遍历过去判断,只需数一下在这个区间里的数,究竟有多少个符合题意。
具体做法是,将0到1000000的数,作为一个数组的下标,初始化为0,如果这个下标不含不吉利的数,那么以这个数为下标的数,改为1。
也就是说,判断的过程只经历了一遍,做了多次的是数数的过程,如果没有这个“预处理”,那我们需要判断多次。(毕竟判断所需的时间比数一遍的时间长)

代码解析

#include <stdio.h>


int main()
{
    int a[1000000] = {0};
    long int i;    
    for (i = 0; i < 1000000; i++) {        
        long int m = i,k = i;        
        while (m) {
            if (m%10 == 4) {
                a[i] = 1;
                break;
            }            
            m /= 10;            
        }        
        while (k) {            
            if (k%100 == 62) {
                a[i] = 1;
                break;
            }            
            k /= 10;            
        } 
    }    
    long int w,r;    
    while (scanf("%ld %ld",&w,&r) != EOF && ( w != 0 || r != 0) ) {
        long int count = 0;
        for (i = w; i <= r; i++) {
            if (a[i] == 0) {
                count++;
            }
        }        
        printf("%ld\n",count); 
    }      
    return 0;
}

题4 HDU – 2037

题目描述

今年暑假不AC

“今年暑假不AC?”
“是的。”
“那你干什么呢?”
“看世界杯呀,笨蛋!”
“@#$%^&*%…”

确实如此,世界杯来了,球迷的节日也来了,估计很多ACMer也会抛开电脑,奔向电视了。
作为球迷,一定想看尽量多的完整的比赛,当然,作为新时代的好青年,你一定还会看一些其它的节目,比如新闻联播(永远不要忘记关心国家大事)、非常6+7、超级女生,以及王小丫的《开心辞典》等等,假设你已经知道了所有你喜欢看的电视节目的转播时间表,你会合理安排吗?(目标是能看尽量多的完整节目)

Input

输入数据包含多个测试实例,每个测试实例的第一行只有一个整数n(n<=100),表示你喜欢看的节目的总数,然后是n行数据,每行包括两个数据Ti_s,Ti_e (1<=i<=n),分别表示第i个节目的开始和结束时间,为了简化问题,每个时间都用一个正整数表示。n=0表示输入结束,不做处理。

Output

对于每个测试实例,输出能完整看到的电视节目的个数,每个测试实例的输出占一行。

Sample Input

12
1 3
3 4
0 7
3 8
15 19
15 20
10 15
8 18
6 12
5 10
4 14
2 9
0

Sample Output
5

解题思路

1.若按开始时间排序,肯定无法获得最优解,因为我们无法避免这样一种情况:开始的最早,结束的最晚,一天下来可能只能看一两个节目;
2.于是我们按结束时间从小到大排序,当一个节目结束,找到离它结束时间最近的一个节目(因为是符合时间里面的 最近一个节目,所以它与同期节目相比,一定是结束的最早的,时间可以用来看更多的节目),发现能获得最优解。(这里排序没用结构体,推荐还是用结构体…)

代码解析

#include<stdio.h>
int main(int argc, const char * argv[]) {

    int i,n,j;

    int tmp;

    while (scanf("%d",&n) != EOF && n != 0) {

        int count = 1;
        int a[100] = {0};
        int b[100] = {0};
        for (i = 0; i < n; i++)             
            scanf("%d %d",&a[i],&b[i]);            

        for (i = 0; i < (n-1); i++) {
            for (j = 0; j < (n-1-i); j++) {
                if (b[j] > b[j+1]) {                    
                    tmp = a[j];
                    a[j] = a[j+1];
                    a[j+1] = tmp;

                    tmp = b[j];
                    b[j] = b[j+1];
                    b[j+1] = tmp;                    
                }
            }
        }

        i = 0;        
        for (j = 1; j < n; j++) {            
            if (a[j] >= b [i]){
                i = j;
                count++;
            }
        }
        printf("%d\n",count);        
    }    
    return 0;    
}

题5 HDU – 5055

题目描述

Bob and math problem

Recently, Bob has been thinking about a math problem.
There are N Digits, each digit is between 0 and 9. You need to use this N Digits to constitute an Integer.
This Integer needs to satisfy the following conditions:
1. must be an odd Integer.
2. there is no leading zero.
3. find the biggest one which is satisfied 1, 2.

Example:
There are three Digits: 0, 1, 3. It can constitute six number of Integers. Only “301”, “103” is legal, while “130”, “310”, “013”, “031” is illegal. The biggest one of odd Integer is “301”.

Input

There are multiple test cases. Please process till EOF.
Each case starts with a line containing an integer N ( 1 <= N <= 100 ).
The second line contains N Digits which indicate the digita1,a2,a3,⋯,an.(0≤ai≤9)

Output

The output of each test case of a line. If you can constitute an Integer which is satisfied above conditions, please output the biggest one. Otherwise, output “-1” instead.

Sample Input
3
0 1 3
3
5 4 2
3
2 4 6

Sample Output
301
425
-1

解题思路

这题关于输出-1的情况有些混乱…
大致如下:
当全为偶数时,输出-1;
当只有1个奇数,其余全为0时,输出-1;
注意n=1时的情况,为奇数,输出这个奇数,为偶数,输出-1;

然后选出最小的奇数,放到最后,再将前n-1个数由大到小排序,输出即可。
(这题输出正确的数不难,难的是对前面各种情况的判断)

代码解析

#include <stdio.h>

int main()
{
    int n,i,j,tmp,count;
    int a[100] = {0};
    int flag = 0;
    int flag1 = 0,flag2 = 0;
    while (scanf("%d",&n) != EOF) {
        flag = 0;
        flag1 = 0;
        flag2 = 0;

        for ( i = 0; i < n; i++) 
            scanf("%d",&a[i]);

        for (i = 0; i < n; i++) 
            if (a[i] % 2 == 1) flag1++;

        for (i = 0; i < n; i++) 
            if (a[i] != 0) flag2++;


        if ((flag1 == 1 && flag2 == 1 && n != 1) || (flag1 == 0)) {
            printf("-1\n");
            continue;

        }

        tmp = 10;
        count = 0;
        flag = 0;
        for (i = 0; i < n; i++) {
            if ( (a[i] % 2 == 1) && (a[i] < tmp) ) {
                tmp = a[i];
                count = i;
                flag = 1;
            }
        }

        if (flag == 1) {
            a[count] = a[n-1];
            a[n-1] = tmp;        
        }

        for (i = 0; i < (n-2); i++) {
            for (j = 0; j < (n-2-i); j++) {
                if (a[j] < a[j+1]) {                    
                    tmp = a[j];
                    a[j] = a[j+1];
                    a[j+1] = tmp;                   
                }
            }
        }

        for (i = 0; i < n; i++) 
            printf("%d",a[i]);

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

题6 HDU – 1789

题目描述

Doing Homework again

Ignatius has just come back school from the 30th ACM/ICPC. Now he has a lot of homework to do. Every teacher gives him a deadline of handing in the homework. If Ignatius hands in the homework after the deadline, the teacher will reduce his score of the final test. And now we assume that doing everyone homework always takes one day. So Ignatius wants you to help him to arrange the order of doing homework to minimize the reduced score.

Input

The input contains several test cases. The first line of the input is a single integer T that is the number of test cases. T test cases follow.
Each test case start with a positive integer N(1<=N<=1000) which indicate the number of homework.. Then 2 lines follow. The first line contains N integers that indicate the deadlines of the subjects, and the next line contains N integers that indicate the reduced scores.

Output
For each test case, you should output the smallest total reduced score, one line per test case.

Sample Input
3
3
3 3 3
10 5 1
3
1 3 1
6 2 3
7
1 4 6 4 2 4 3
3 2 1 7 6 5 4

Sample Output
0
3
5

解题思路

1.先按分数由大到小排序,分数相同时,按时间由小到大排序,于是我们按这个排序去处理,会先处理分数大的;
2.创建一个数组,初始化为0。以每一对数的截止日期为下标,将数组中的内容改为1(有数为1,无数为0);
3.但是很显然,我们还不能直接按这个排序,将所有数直接输入数组中,因为有一些数的截止日期相同,那如何来处理截止日期相同的情况?
4.当遇到数组中内容为1的情况时,说明有一个与你截止日期相同,但分数更大的数已经占据了这个位置,那我们从在数组中,以这个截止日期为下标往前找,当找到空位时,将数字填入(把0改为1)。如果找不到空位,将分数累计,最后输出。
5.思想与哈希表中开放寻址法解决冲突的思想类似。

代码解析

#include <stdio.h>

struct _homework{    
    int deadline;
    int scorce;    
};


int main()
{
    struct _homework homew[1000];
    struct _homework tmp;
    int T,N;
    int i,j,k;
    int m;

    scanf("%d",&T);

    for (m = 0; m < T; m++) {

        scanf("%d",&N);
        for (j = 0; j < N; j++)
            scanf("%d",&homew[j].deadline);

        for (j = 0; j < N; j++)
            scanf("%d",&homew[j].scorce);

        for (j = 0; j < N-1; j++) {
            for (k = 0; k < N-1-j; k++) {
                if (homew[k].scorce < homew[k+1].scorce) {
                    tmp = homew[k];
                    homew[k] = homew[k+1];
                    homew[k+1] = tmp;

                }  else if (homew[j].scorce == homew[j+1].scorce) {                    
                    if (homew[j].deadline > homew[j+1].deadline) {                        
                        tmp = homew[j];
                        homew[j] = homew[j+1];
                        homew[j+1] = tmp;
                    }
                }
            }
        }
        int a[1001] = {0};        
        int sum = 0;        
        for (i = 0; i < N; i++) {            
            if ( a[homew[i].deadline] == 0 )
                a[homew[i].deadline] = 1;            
            else{
                int flag = 0;
                j = homew[i].deadline - 1;
                for (; j >= 1; j--) {                        
                    if (a[j] == 0){
                        a[j] = 1;
                        flag = 1;
                        break;
                    }                    
                }
                if (flag == 0) 
                    sum = sum + homew[i].scorce;                
            }            
        }
        printf("%d\n",sum);  
    }
    return 0;
}

题7 HDU – 6188

题目描述

Duizi and Shunzi

Nike likes playing cards and makes a problem of it.

Now give you n integers, ai(1≤i≤n)
We define two identical numbers (eg: 2,2) a Duizi, and three consecutive positive integers (eg: 2,3,4) a Shunzi.
Now you want to use these integers to form Shunzi and Duizi as many as possible.
Let s be the total number of the Shunzi and the Duizi you formed.
Try to calculate max(s)max(s).
Each number can be used only once.

Input
The input contains several test cases.
For each test case, the first line contains one integer n(1≤n≤106).
Then the next line contains n space-separated integers a (1≤ai≤n)

Output
For each test case, output the answer in a line.

Sample Input
7
1 2 3 4 5 6 7
9
1 1 1 2 2 2 3 3 3
6
2 2 3 3 3 3
6
1 2 3 3 4 5

Sample Output
2
4
3
2

解题思路

1.将所有牌放入一个数组中,当然不是直接放入,而是把牌的点数作为数组下标,例如,有1张5点的牌,则数组中下标为5的空间,存放1;有3张6点的牌,则数组中下标为6的空间,存放3;
2.顺子需要三张牌,对子只需要两张,所以我们尽量多组对子;3.当牌数>2时,不难发现,这些牌尽量先组对子,剩下两张牌再具体讨论;
4.当牌数<=2时,我们需要分情况讨论,到底是组顺子好还是组对子好;
5.当牌数为偶数张,直接组对子,假如这时候有两张,我们已经能组成一对了,但是如果考虑去组顺子的话,需要后面两张也都有牌(即使有牌,也不会大于组对子能组成的数量);
6.当牌数为奇数,且下一张也为奇数,第三张有牌时才考虑去组顺子。

代码解析

#include <stdio.h>

long int k[1000000] = {0};

int main()
{
    long int n,i,d,count;    
    while (scanf("%ld",&n) != EOF) {        
        count = 0;
        for ( i = 0; i < n; i++) {
            scanf("%ld",&d);
            k[d]++;
        }          
        for (i = 1; i < 1000000; i++) {
            if (k[i]%2 == 0) 
                count += k[i]/2;                        
            else if (k[i]%2 == 1 && k[i+1]%2 == 1 && k[i+2] > 0) {                
                count += (1 + k[i]/2 + k[i+1]/2 );
                k[i+2]--;
                i++;                
            }
            else {
                count += (k[i]/2 + k[i+1]/2);
                i++;
            }            
        }        
        printf("%ld\n",count);
        for (i = 1; i < 1000000; i++) 
            k[i] = 0;                
    }
    return 0;
}

题八 POJ – 1700

题目描述

Crossing River

A group of N people wishes to go across a river with only one boat, which can at most carry two persons. Therefore some sort of shuttle arrangement must be arranged in order to row the boat back and forth so that all people may cross. Each person has a different rowing speed; the speed of a couple is determined by the speed of the slower one. Your job is to determine a strategy that minimizes the time for these people to get across.

Input
The first line of the input contains a single integer T (1 <= T <= 20), the number of test cases. Then T cases follow. The first line of each case contains N, and the second line contains N integers giving the time for each people to cross the river. Each case is preceded by a blank line. There won’t be more than 1000 people and nobody takes more than 100 seconds to cross.

Output
For each test case, print a line containing the total number of seconds required for all the N people to cross the river.

Sample Input
1
4
1 2 5 10

Sample Output
17

解题思路

经典的小船过河问题,大意是说,每次过河,小船只能载两个人,速度为较慢的那个人的速度,而且小船过去以后,还需要一个人开回来,问最少用多长时间。
由于每次过去两个人,我们从最慢的人开始逐渐送过去。那么有两种最有优情况:
1.最快的和次快的过河,然后最快的将船划回来;次慢的和最慢的过河,然后次快的将船划回来
2.最快的和最慢的过河,然后最快的将船划回来,最快的和次慢的过河,然后最快的将船划回来
而其他的情况都会比这种情况慢。
经过这样一轮,最慢的和次慢的就被送过去了。循环即可。

代码解析

#include <iostream>
using namespace std;

int main()
{
    int a[1000], t, n, sum;
    scanf("%d", &t);
    while(t--) {
        scanf("%d",&n); // n个人
        sum = 0; // 时间总和

        for(int i = 0; i < n; ++i)
            scanf("%d",&a[i]);

        //重复上述两种方法,该方法每次循环运送两个人,则时间根据最小的进行叠加
        while(n > 3) {
            // 最快的和次快的过河,然后最快的将船划回来;次慢的和最慢的过河,然后次快的将船划回来
            sum += min(a[1] + a[0] + a[n - 1] + a[1],
                       // 最快的和最慢的过河,然后最快的将船划回来,最快的和次慢的过河,然后最快的将船划回来
                       a[n - 1] + a[0] + a[n - 2] + a[0]);
            n -= 2;
        }
        if(n == 3)
            sum += a[0] + a[1] + a[2]; //当人数为三时。两种方法都只剩 0 1 2 了
        else if(n == 2)
            sum += a[1];
        else
            sum += a[0];

        printf("%d\n",sum);        
    }
}

题九 POJ1328

题目描述

Radar Installation

Assume the coasting is an infinite straight line. Land is in one side of coasting, sea in the other. Each small island is a point locating in the sea side. And any radar installation, locating on the coasting, can only cover d distance, so an island in the sea can be covered by a radius installation, if the distance between them is at most d.
We use Cartesian coordinate system, defining the coasting is the x-axis. The sea side is above x-axis, and the land side below. Given the position of each island in the sea, and given the distance of the coverage of the radar installation, your task is to write a program to find the minimal number of radar installations to cover all the islands. Note that the position of an island is represented by its x-y coordinates.

Input

The input consists of several test cases. The first line of each case contains two integers n (1<=n<=1000) and d, where n is the number of islands in the sea and d is the distance of coverage of the radar installation. This is followed by n lines each containing two integers representing the coordinate of the position of each island. Then a blank line follows to separate the cases.
The input is terminated by a line containing pair of zeros

Output

For each test case output one line consisting of the test case number followed by the minimal number of radar installations needed. “-1” installation means no solution for that case.

Sample Input

3 2
1 2
-3 1
2 1

1 2
0 2

0 0

Sample Output

Case 1: 2
Case 2: 1

解题思路

题目给出了点的座标,需要将这些座标转换为区间。如下:
《贪心算法题型总结》
如图,区间为 [x – t, x + t] 。

再算出下一个点是否在这个点的范围内,若不在,需要点数增加1。
(注意,由于懒所以这并不是最终的AC代码,而是解题思路,所以输入输出格式没有按照题目要求来,也没有分组计算)

代码解析

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

struct Point {  
    double sta;
    double end;
} point[1000];

int cmp(const Point& a, const Point& b) { 
    return a.sta > b.sta ? 1 : -1;
} 

int main()
{   
    int n, d; // 海岛个数、雷达半径
    cin >> n >> d; 
    int counting = 1; // 所需个数 

    for(int i = 0; i < n; ++i) { 
        int x, y;
        cin >> x >> y;
        // 超出范围 
        if(y > d)
            counting = -1;

        double t = sqrt(d * d - y * y);

        //转化为最少区间的问题
        //区间左端点
        point[i].sta = x - t;
        //区间右端点
        point[i].end = x + t;
    }

    //按区间左端点排序
    if (counting != -1)
        sort(point, point + n, cmp);

    //区间起始右端点
    double s = point[0].end;

    for(int i = 1; i < n; ++i) { 
        if (point[i].sta > s) {
            //如果两个区间没有重合,增加雷达数目并更新右端点 
            ++counting;
        }
        s = point[i].end;
    } 
    cout << counting << endl;
    system("pause");
    return 0;
} 
点赞