貪心題型總結

貪心算法(英語:greedy algorithm),又稱貪婪算法,是一種在每一步選擇中都採取在當前狀態下最好或最優(即最有利)的選擇,從而希望導致結果是最好或最優的算法 —維基百科

貪心是一種解決問題的策略.如果策略正確,那麼貪心法往往是易於描述,易於實現的. —劉汝佳

題目類型 :
   1. 最優裝載問題
   2. 部分揹包問題
   3. 乘船問題
   4. 選擇不相交區間
   5. 區間選點問題
   6. 區間覆蓋問題
   7.任務調度問題
   8.Huffman編碼
由於 Huffman編碼我暫時也搞的也不是很清楚 就不 班門弄斧 了 ,如果想看Huffman 可以先走了,不用耽誤時間了.搞清楚之後,在更新

最優裝載問題

題幹 : 給出n個物體,第 i 個物體重量爲 wi,選擇儘量多 的物體,使得總重量不超過 c

由於只關心個數,而忽略重量 .
策略 : 每次選當先剩下物體中重量最小的物體,在 c 的範圍內 .

部分揹包問題

題幹 : 有 n 個物體,第 i 個物體的重量爲 wi,價值爲 vi.在總重量不超過 c 的情況下讓總價值儘量高,每一個物體都可以只取走一部分,價值和重量按比例計算.

這類題型 ,選擇重點就是 我們經常所說的 性價比

策略 : 每次所選的 是剩下物體裏面 性價比 最高的物體.當揹包裝不下 整個物體時,可以按照 百分比 選擇其中的一部分

乘船問題

題幹 : 有 n 個人,第 i 個人重量 爲wi,美艘 船的最大載重量均爲 c ,且最多隻能乘 2 個人,用最少的船裝載所有人.

由於所求的是 用最少的船裝載 ,所以每次儘量 讓 2 個人 同時乘船 當 船的重量允許時.
策略 : 在船的載重量的範圍內 ,每次選擇時, 讓當前所有人中 最輕 的 當前所有人中最重 的人乘坐 一艘船,這樣就會使船 的載重量 浪費最少.

當最輕的人 無法和 船上的任意 一人同坐一艘船時,剩下的所有人 都只能一人坐一艘船了 .
因爲 當最輕的人都無法 和 別人一起乘坐時 ,再剩下的所有人中,任意兩人 的重量 都大於 最輕的人和另一個人的重量,所以只能一人乘坐一艘船了

選擇不相交區間

題幹 : 數軸上有n 個開區間(ai,bi).選擇儘量多個區間,使得這些區間兩兩沒有公共點.

按照 bi從小到大的順序給區間排序

策略 :每次選擇的是目前不相交區間裏的第一個區間.

原因

當 a1 == a2 時

因爲 要選擇儘量多的區間,所以浪費的區間越小,其他的區間能利用的空間就越大,很明顯 選擇是第一個

當 a1 > a2 時 
在這裏插入圖片描述
同上, 當在這 所浪費的空間只有 b2 - b1,而 a1 - a2 的空間不算浪費 ,想想爲什麼

當 a1 < a2 時
在這裏插入圖片描述

因爲 每選擇一個區間 後,這個區間 的 起始端 到 它前面 一個區間的 末端 這塊 區間 是無法利用的,也就是 b0 到 a1這塊區間是利用不到的,減少浪費 是因爲 爲了後面的區間 能利用上這塊區間,當利用不 上的時候 節省 也就沒有了意義.
換句話來講,每次選擇的 是 剩下區間 裏 bi 最小的一個.當然在不相交的前提下.

在這裏插入圖片描述

hdu 2037 —今年暑假不ac

#include<iostream>
#include<algorithm>

using namespace std;

struct node
{
	int start;
	int end;
};
struct node a[105];

int cmp(struct node a,struct node b)
{
	return a.end < b.end;
}

int main( )
{
	int n;
	while(cin >> n && n != 0)
	{
		for(int i = 1;i <= n;i++)  cin >> a[i].start >> a[i].end;

		sort(a+1,a+n+1,cmp);
		int cnt = 1;
		int temp = a[1].end;
		for(int i = 2;i <= n;i++)
		{
			if(temp <= a[i].start)
			{
				cnt++;
				temp = a[i].end;
			}
		}

		cout << cnt << endl;
	}
}
區間選點問題

題幹 : 數軸上有 n 個閉區間[ai,bi],取儘量少的點,使得每個 區間內 至少有一個點(不同區間含的點 可以是 同一個)

按照 b 從小到大 排序

策略 : 每次選取 當前區間的最後 一個點

原因

在這裏插入圖片描述

如上圖 : 如果區間選擇 了 (a1,b1) 時,只有在點 D 是,纔是最正確的點.也是 目前 最好的選擇

Radar Installation

#include<iostream>
#include<algorithm>
#include<cmath>

using namespace std;

struct node
{
	double start;
	double end;
};

struct node a[1005];

int cmp(struct node a,struct node b)
{
	if(a.end != b.end)  return a.end < b.end;
	else return a.start > b.start;
}

int main( )
{
	int n,radius;
	double tempx,tempy;
	int flag;
	int num = 1;
	while(cin >> n >> radius && n != 0)
	{
		flag = 0;
		for(int i = 1;i <= n;i++)
		{
			cin >> tempx >> tempy;
			if(tempy > radius) flag = 1;
			a[i].start = tempx - sqrt((radius * radius *1.0) - (tempy * tempy*1.0));
			a[i].end = tempx + sqrt((radius * radius *1.0) - (tempy * tempy*1.0));
		}

		if(flag == 1) 
		{
			cout << "Case " << num << ": -1" << endl;
			num++;
			continue;
		}

		sort(a+1,a+n+1,cmp);
		
		int cnt = 1;
		double temp = a[1].end;
		for(int i = 2;i <= n;i++)
		{
			if(a[i].start <= temp) continue;
			else 
			{
				cnt++;
				temp = a[i].end;
			}
		}
		cout << "Case " << num << ": " << cnt << endl;
		num++;
	}

	return 0;
}
區間覆蓋問題

數軸上有n 個閉區間[ai,bi],選擇 儘量少的區間覆蓋一條指定線段[s,t]

把各區間按照 ai 從小到大排序

策略 : 如果只有一個起點,就選擇這個起點,如果是多個起點則 在每次起點 上的 所有區間裏 選擇 最長的區間.(也就是 bi 最大的區間)

在這裏插入圖片描述
若,起點爲 a1,那當然 只能選擇 (a1,b1)區間了,若起點 爲 A ,則選擇的是 區間(a2,b2).因爲 相同的起點,當然 b2 越長,所選擇的 區間就越少了.
這類題思想簡單,實現 時 就稍微 困難點.
Minimal coverage

#include<iostream>
#include<algorithm>
#include<cstring>

using namespace std;

struct node
{
	int start;
	int end;
};

struct node a[100005];
int book[100005];

int cmp(struct node a,struct node b)
{
	return a.start < b.start;
}

int main( )
{
	int T;
	cin >> T;
	while(T--)
	{
		memset(book,0,sizeof(book));
		int len;
		cin >> len;
		int i = 1;
		int tempstart,tempend;
		while(cin >> tempstart >> tempend && (tempstart || tempend))
		{
			if(tempend <= 0 || tempstart >= len)  continue;
			a[i].start = tempstart;
			a[i].end = tempend;
			i++;
		}
		sort(a+1,a+i,cmp);
		if(a[1].start > 0)
		{
			cout << 0 << endl;
			continue;
		}
		int temp = 0,tempmax = 0,coordinate;
		int t = -1;   //用來備份起點相同 的上一個 區間
		for(int j = 1;j < i;j++)
		{
			if(a[j].start <= temp)
			{
				if(a[j].end > tempmax)
				{
					book[j] = 1;
					if(t != -1)   book[t] = 0;
					tempmax = a[j].end;
					t = j;
				}
			}
			else
			{
				temp = tempmax;
				t = -1;
				j--;
			}
			if(tempmax >= len)  break;
		}

		if(tempmax < len)
		{
			cout << 0 << endl;
			continue;
		}

		int cnt = 0;
		for(int j = 1;j < i;j++)	if(book[j]) cnt++;

		

		cout << cnt << endl;
			for(int j = 1;j < i;j++)
			{
				if(book[j])
				{
					cout << a[j].start << " " << a[j].end << endl;
				}
			}

	}

	return 0;
}
任務調度問題

題幹 : 給定 n 向任務,每項任務的開始時間 爲si,結束時間爲ei,(1 <= i <= n,0<= si< ei),且沒想任務只能在一臺機器上完成每臺機器一次只能完成一項任務.如果任務 i 和任務 j 滿足 ei <= sj 或 ej < si.則任務 i 和 任務 j 是不衝突的,可以在一臺機器上完成

對每項任務開始時間 升序進行排序
最少用的機器臺數爲 m = 0
策略 : 每次選擇當前最小開始時間的任務
if(任務 i 和已經執行的任務不衝突) 安排任務 i 在空閒的機器上完成
else
{
   m++; //添加一臺新機器
   任務 i 在新機器完成
}

Schedule

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

typedef pair<int ,int> pii;//時間點,前一個表示時間點的是兼職,後一個表示這個時間點是開始時間還是結束時間
const int N = 1e5 + 10;
pii a[2*N];  //2n 個時間點

int l[N],r[N];  //l  記錄機器的開機時間   ,r 機器的關機時間

int main()
{
	int t;
	long long sum = 0;
	scanf( "%d",&t);
	while(t--)
	{
		int n;//一共n 項任務
		scanf( "%d",&n);

		for(int i = 1;i <= n;i++)
		{
			int left,right;
			scanf("%d%d",&left,&right);
			a[2*i - 1] = pii(left,1);
			a[2*i] = pii(right,-1);
		}

		sort(a+1,a+2*n+1);
		memset(l,-1,sizeof(l));
		memset(r,-1,sizeof(r));

		int num = 0,ans = 0;  //num 表示當前運行的機器數量  ans 表示到當前一共開過多少臺機器

		for(int i = 1;i <= 2*n;i++)
		{
			if(a[i].second == 1)
			{
				num++;
				if(l[num] == -1)   l[num] = r[num] = a[i].first;//num 是新開的機器
					ans = max(ans,num);
			}
			else
			{
				r[num] = a[i].first;
				num--;
			}
		}
			sum = 0;
			for(int i = 1;i <= ans;i++)
			{
				sum +=(r[i] - l[i]);
			}

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