貪心算法感悟

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,不免有些擔心能不能真正掌握。

 

 

 

 

 

 

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