贪心算法感悟

1,贪心想法

在做了一段时间的贪心题后,感觉到贪心思想真是妙不可言,虽然有局限性但仅仅在全是贪心的题集中我也疲于应对,一度思维混乱,真是想破头也想不出题解。如何在最优解问题的过程中,依据贪心标准,直接去求每一步的最优解,通过若干次的贪心选择,最终得出整个问题的最优解?只有看了解题报告才恍然大悟,甚至有时看了解题报告也理不清思路,下面写几个简单例题理一下基本思路:

2,例题

这是课件上的一道题,我觉得贪心思想非常明确,在这里写下来。

1,给定一个载重量为M的揹包,考虑n个物品,其中第i个物品的重量 价值wi 1in),要求把物品装满揹包,且使揹包内的物品价值最大

两类揹包问题(根据物品是否可以分割),如果物品不可以分割,称为01揹包问题动态规划;如果物品可以分割,则称为揹包问题贪心算法

//形参n是物品的数量,c是揹包的容量M,数组a是按物品的性价比降序排序
double knapsack(int n, bag a[], double c)
{
  double cleft = c;        //揹包的剩余容量
  int i = 0;
  double b = 0;          //获得的价值
  //当揹包还能完全装入物品i
  while(i<n && a[i].w<cleft)      //贪心思想的体现。
  {
    cleft -= a[i].w;
    b += a[i].v;
    i++;
  }
  //装满揹包的剩余空间
  if (i<n) b += 1.0*a[i].v*cleft/a[i].w;
  return b;
}

2,.活动选择问题

有n个需要在同一天使用同一个教室的活动a1,a2,…,an,教室同一时刻只能由一个活动使用。每个活动ai都有一个开始时间si和结束时间ji 。一旦被选择后,活动ai就占据半开时间区间[si,ji)。如果[si,ji]和[sj,ji]互不重叠,ai和aj两个活动就可以被安排在这一天。问题就是要安排这些活动使得尽量多的活动能不冲突的举行。

理解:

贪心策略应该是每次选取结束时间最早的活动。直观上理解,按这种方法选择相容活动为未安排活动留下尽可能多的时间。这也是把各项活动按照结束时间单调递增排序的原因。至于为什么算持续时间然后排序先选持续时间短的不行给出一个例子:

设几个时间段 (1,4)  (3,5)   (4,7)  (6,8)   (7,10)差分别为3,2,3,2 

选短活动算法得出结果为选活动区间(3,5)和(6,8)
实际最优解为(1,4)(4,7)(7,10)

关键代码如下:

int greedy_activity_selector()  { 
 	int num=1,i=1; 
      for(int j=2;j<=N;j++)
      {  
        if(act[j].start>=act[i].end) 
         {  
            i=j;  
            num++; 
         }    
  }    
  return num;
}

3,多机调度问题

n个作业组成的作业集,可由m台相同机器加工处理。要求给出一种作业调度方案,使所给的n个作业在尽可能短的时间内由m台机器加工处理完成。作业不能拆分成更小的子作业;每个作业均可在任何一台机器上加工处理。

理解: 

这也是经典的题型,当n<=m时,只要将作业时间区间分配给作业即可;当n>m时,首先将n个作业从大到小排序,然后依此顺序将作业分配给空闲的处理机。也就是说从剩下的作业中,选择需要处理时间最长的,然后依次选择处理时间次长的,直到所有的作业全部处理完毕,或者机器不能再处理其他作业为止。如果我们每次是将需要处理时间最短的作业分配给空闲的机器,那么可能就会出现其它所有作业都处理完了只剩所需时间最长的作业在处理的情况,这样势必效率较低。

部分代码加解析如下:

int main()  {
  	int n,m;

    int speed[10010];  

    int mintime[110]; 
         	memset(speed,0,sizeof(speed));  //数组speed,mintime全初始化为0
 	memset(mintime,0,sizeof(mintime));   
 	cin>>n>>m;     
	for(int i=0;i<n;++i) cin>>speed[i]; 
     sort(speed,speed+n,cmp);  
    for(int i=0;i<n;++i)  
     {   
  	*min_element(mintime,mintime+m)+=speed[i];  //求数组里的最小值,相当于结束早再排上另一个作业
    	}  
     cout<<*max_element(mintime,mintime+m)<<endl;所有作业排完后选时间最长的
 }

4,Game Prediction

 

Suppose there are M people, including you, playing a special card game. At the beginning, each player receives N cards. The pip of a card is a positive integer which is at most N*M. And there are no two cards with the same pip. During a round, each player chooses one card to compare with others. The player whose card with the biggest pip wins the round, and then the next round begins. After N rounds, when all the cards of each player have been chosen, the player who has won the most rounds is the winner of the game. 
Given your cards received at the beginning, write a program to tell the maximal number of rounds that you may at least win during the whole game. 

Input

The input consists of several test cases. The first line of each case contains two integers m (2?20) and n (1?50), representing the number of players and the number of cards each player receives at the beginning of the game, respectively. This followed by a line with n positive integers, representing the pips of cards you received at the beginning. Then a blank line follows to separate the cases. 
The input is terminated by a line with two zeros.

Output

For each test case, output a line consisting of the test case number followed by the number of rounds you will at least win during the game. 
 

Sample Input

2 5
1 7 2 10 9

6 11
62 63 54 66 65 61 57 56 50 53 48

0 0


Sample Output

Case 1: 2
Case 2: 4
题目大意:有m个人玩牌,每个人有n张牌,所以牌的总数是m*n,从1到m*n不重复,给出了你拥有的牌,比赛规则是每个人依次出牌,最后数字大的那个人获胜,找出至少能赢几次。

作业上的一道题完全想不到:

要求至少赢几次就意味着没有运气成分在里面,相当于明牌。自己出一张牌除非别人手里没有比你大的否则必定输,而且别人手里如果不止一张大的牌,肯定先出较小的一张。解题思路是从大往小出 ,就用大牌压


#include<bits/stdc++.h>
using namespace std;
int main()
{
    int n, cardNum, t, cnt = 0;
    int card[1001];
    while (1) {
        cin >> n >> cardNum;
        if (!n)
            break;
        memset(card, 0, sizeof(card));
        for(int i = 0;i<cardNum;++i) {
            cin >> t;
            card[t] = 1;//标记自己的牌
        }
        int win = 0, bigger = 0; //win是赢的次数,bigger是别人比自己大的牌的张数
        for (int i = n * cardNum; i > 0;--i) {//从所有牌的最大开始判定
            if (card[i]) { //是自己的牌
                if (!bigger)//如果!bigger不是0执行,即bigger是0执行,意味着没有牌比这张大
                    ++win;
                else
                    --bigger;//bigger不是0执行此条,意味着这张牌不是最大,之前有更大牌出现在别人手里
            }
            else
                ++bigger;
        }
        cout << "Case " << ++cnt << ": " << win << endl;
    }
}

关于贪心的理解真的觉得还有好长的路要走,又刚学了dp,不免有些担心能不能真正掌握。

 

 

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章