貪欲(2)イベントスケジューリングの問題

目次

1.イベントスケジューリングの問題

第二に、OJの実際の戦闘

CSU 1065:科学会議

CodeForces 589Fはどのくらい食べることができますか?

HDU1789再び宿題をする

HDU4864タスク

HDU1051木の棒


1.イベントスケジューリングの問題

イベントスケジューリング問題の性質は、Tian Jiの競馬と同じであり、本質的に順序付けの問題です。

 

第二に、OJの実際の戦闘

CSU 1065:科学会議

トピック:

説明

        学会の機能は通常、いくつかの同時セクションに分かれています。たとえば、並列コンピューティングに関するセクション、視覚化に関するセクション、データ圧縮に関するセクションなどがあります。
       明らかに、会議の科学的プログラムの時間を短縮し、宴会、お茶を飲む、および非公式の議論のためのより多くの時間を確保するために、いくつかのセクションの同時作業が必要です。ただし、興味深いレポートが異なるセクションで同時に提供される可能性があります。
       参加者は、彼にとって興味深いすべてのレポートのタイムテーブルを書き留めました。彼はあなたに彼が出席できるレポートの最大数を決定するように頼みます。

入力

        最初の行には、1≤N≤100000の興味深いレポートが含まれています。次のN行のそれぞれには、スペースで区切られた2つの整数TsとTeが含まれています(1≤Ts<Te≤30000)。これらの数値は、対応するレポートが開始および終了する回数です。時間は、会議の開始から分単位で測定されます。

出力

        参加者が参加できるレポートの最大数を出力する必要があります。参加者は2つのレポートに同時に参加することはできず、参加する2つのレポートは少なくとも1分離す必要があります。たとえば、レポートが15で終了する場合、出席できる次のレポートは16以降で開始する必要があります。

サンプル入力

5
3 4
1 5
6 7
4 5
1 3

サンプル出力

3

コード:

#include<iostream>
#include<algorithm>
using namespace std;
 
struct node
{
	int s, e;
}nod[100005];
 
bool cmp(node a, node b)
{
	return a.e < b.e;
}
 
int main()
{
	int n, ans = 0, k = 0;
	cin >> n;
	for(int i=0;i<n;i++)cin >> nod[i].s >> nod[i].e;
	sort(nod, nod + n, cmp);
	for (int i = 0; i < n; i++)if (k < nod[i].s)k = nod[i].e, ans++;
	cout << ans;
	return 0;
}

CodeForces 589Fはどのくらい食べることができますか?

トピック:

説明

宴会場にグルメがやって来て、料理人が ゲストにn品を提案して くれました。グルメはスケジュールを知っています:各料理がいつ提供されるか。

以下のために 私は、彼が時間内に2つの整数の瞬間を知っている料理番目の Iを し、  bはI  (宴会の先頭から秒単位) -コックをもたらすとき 、私ホールに目の皿を、彼らはそれを実行する時期(a i  <  b i)。たとえば、 私は = 10及び bはiが 11 =、次に I番目の皿は、1秒間に食べるために利用可能です。

料理は非常に大量にあるので、料理が食べられる限り(つまり、ホールにいる間)、品切れになることはありません。

グルメは、n 種類の料理をそれぞれ試し 、料理人を怒らせたくないと考えています。そのため、グルメはそれぞれの料理を同じ時間食べたいと思っています。食事中、グルメは即座に料理を切り替えることができます。料理の切り替えは、整数の瞬間にのみ許可されます。グルメは同時に1皿しか食べることができません。他の料理を食べた後は、料理に戻ることができます。

グルメは、上記の条件に違反することなく、宴会でできるだけ長く食べたいと考えています。彼を助けて、彼が宴会で料理を食べることができる最大合計時間を見つけることができますか?

入力

入力の最初の行は、整数含ま N(1≤  N 宴会に食器の数- ≤100)。

次の n 行には、料理の入手可能性に関する情報が含まれています。 I番目の行は、二つの整数含ま I および Bは、I(0≤  A I  <  B I  ≤10000) -時間の瞬間 I番目の皿は食べてのために利用可能になったときには、  I番目の皿を離れてから取得されホール。

出力

出力には整数のみを含める必要があります—グルメが宴会で料理を食べることができる最大合計時間。

グルメは即座に料理を切り替えることができますが、時間の整数の瞬間に限られます。他の料理を食べた後は、料理に戻ることができます。また、いつでも彼は一皿しか食べることができません。

サンプル入力

入力

3 
2 4 
1 5 
6 9

出力

6

入力

3 
1 2 
1 2 
1 2

出力

0

ヒント

最初の例では、グルメは2番目の料理を1秒間(時間1の瞬間から時間2の瞬間まで)食べ、次に最初の料理を2秒間(2から4)食べてから、2番目の料理に戻ります。 1秒間の皿(4から5)。その後、彼は3番目の料理を2秒間(6から8まで)食べます。

2番目の例では、3つの料理があるため、グルメは各料理を少なくとも1秒間食べることはできませんが、1秒間(1から2)しか利用できません。

このトピックは、先輩が貪欲でできると言った後にも作成されました。
教室の配置が一番良いのは問題のように感じますが、それでも欲張り戦略がまったく同じになるとは思っていませんでした。終了時間が最も早いものを選んでください。
もちろん、食べる時間が十分にある料理は除外する必要があります。
まだまだ弱すぎると深く感じています。
当初、アイデアは非常に不明確でしたが、最終的にノード配列以外に他の配列は必要ないことがわかりました。
また、ok関数は、再帰よりもforループを使用して直接記述する方がはるかに簡単です。

コード:

#include<iostream>
using namespace std;
 
struct node		//d表示时间
{
	int a;
	int b;
	int d;
};
 
bool ok(int m, node *p, int n)		//m表示时间
{
	if (m == 0)return true;
	for (int i = 0; i < n; i++)p[i].d = m;
	for (int k = 0; k < 10000; k++)		//函数ok是非递归的,主要就是这个循环
	{
		int end = 10001, key = -1;		//最后一次修改是把10000改成了10001
		for (int i = 0; i < n; i++)
		{
			if (p[i].a <= k && p[i].b>k && p[i].d>0 && end > p[i].b)		/贪心策略//
			{
				end = p[i].b;
				key = i;
			}
		}
		if (key >= 0)p[key].d--;
	}
	for (int i = 0; i < n; i++)if (p[i].d)return false;
	return true;
}
 
int main()
{
	int n;
	while (cin >> n)
	{
		node *p = new node[n];
		int dif = 10000;
		for (int i = 0; i < n; i++)
		{
			cin >> p[i].a >> p[i].b;
			if (dif>p[i].b - p[i].a)dif = p[i].b - p[i].a;
		}
		int low = 0, high = dif;
		int mid;
		while (low +1 < high)		//二分查找答案
		{
			mid = (high + low) / 2;
			if (ok(mid,p,n))low = mid;
			else high = mid - 1;
		}
		if (ok(high, p, n))cout << high*n << endl;
		else cout << low*n << endl;
	}
	return 0;
}

HDU1789再び宿題をする

トピック:

説明

zichenは30回目のACM / ICPCから学校に戻ったばかりです。今、彼はやるべき宿題がたくさんあります。すべての教師は彼に宿題を提出する期限を与えます。締め切り後にジチェンが宿題を提出した場合、教師は最終テストのスコアを下げます。そして今、私たちはみんなの宿題をするのにいつも一日かかると仮定しています。したがって、zichenは、スコアの低下を最小限に抑えるために、宿題をする順序を調整するのを手伝ってほしいと思っています。

入力

入力にはいくつかのテストケースが含まれています。入力の最初の行は、テストケースの数である単一の整数Tです。Tテストケースが続きます。 
各テストケースは、宿題の数を示す正の整数N(1 <= N <= 1000)で始まります。その後、2行が続きます。最初の行には、被験者の締め切りを示すN個の整数が含まれ、次の行には、減少したスコアを示すN個の整数が含まれます。

出力

テストケースごとに、最小の合計削減スコアを出力する必要があります。テストケースごとに1行です。

サンプル入力33
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 

サンプル出力

0
3

この問題は明らかに貪欲の問題であり、グローバルな最適解が貪欲で見つかることを確認することは難しくありません。

私の考えは、コスト(ペナルティ)スコアに従って並べ替え、特定の日の正確な時間にスコアの降順でジョブを配置し、配置できない場合はスコアをカウントすることです。

宿題は期日にやってみてください。その日の宿題がある場合は前に進み、移動できない場合は捨ててください(ペナルティがあります)。

たとえば、サンプル3の場合、(4,7)(2,6)(4,5)(3,4)(1,3)(4,2)(6,1)としてソートされた7つのジョブがあります。

まず、4日目に(4,7)を実行し、次に2日目に(2,6)を実行し、次に3日目に(4,5)を実行し、次に1日目に(3,4)を実行します。

そして、(1,3)はできない、3を罰する、(4,2)はできない、2を罰する、そして(6,1)は6日目にそれを行う、合計を罰する5の。

もちろん、同じスコアの2つの仕事に遭遇した場合、それは最大の日で最前列にあるはずです。

コード:

#include<algorithm>
#include<iostream>
#include<string.h>
using namespace std;
 
struct node
{
	int day;
	int score;
};
 
node nod[1000];
 
bool cmp(node a, node b)
{
	if (a.score > b.score)return true;
	if (a.score < b.score)return false;
	return a.day>b.day;
}
 
int main()
{
	int t,n;
	cin >> t;
	int max, sum;
	while (t--)
	{
		cin >> n;
		max = 0;
		for (int i = 0; i < n; i++)
		{
			cin >> nod[i].day;
			if (max < nod[i].day)max = nod[i].day;
		}
		for (int i = 0; i < n; i++)cin >> nod[i].score;
		sort(nod, nod + n, cmp);	
		int *list = new int[max+1];
		memset(list, 0, (max+1)*4);
		sum = 0;
		for (int i = 0; i < n; i++)
		{
			int j = nod[i].day;
			while (j > 0 && list[j])j--;
			if (j == 0)sum += nod[i].score;
			else list[j] = 1;
		}
		cout << sum << endl;
		delete list;
	}
	return 0;
}
<algorithm>
#include<iostream>
#include<string.h>
using namespace std;

struct node
{
	int day;
	int score;
};

node nod[1000];

bool cmp(node a, node b)
{
	if (a.score > b.score)return true;
	if (a.score < b.score)return false;
	return a.day>b.day;
}

int main()
{
	int t,n;
	cin >> t;
	int max, sum;
	while (t--)
	{
		cin >> n;
		max = 0;
		for (int i = 0; i < n; i++)
		{
			cin >> nod[i].day;
			if (max < nod[i].day)max = nod[i].day;
		}
		for (int i = 0; i < n; i++)cin >> nod[i].score;
		sort(nod, nod + n, cmp);	
		int *list = new int[max+1];
		memset(list, 0, (max+1)*4);
		sum = 0;
		for (int i = 0; i < n; i++)
		{
			int j = nod[i].day;
			while (j > 0 && list[j])j--;
			if (j == 0)sum += nod[i].score;
			else list[j] = 1;
		}
		cout << sum << endl;
		delete list;
	}
	return 0;
}

動的配列の場合、memset(list、0、(max + 1)* 4); listはポインターであり、その長さは4であるため、長さにはsizeof(list)を使用しないでください。

さらに、memsetはバイトを初期化します(これがmemsetが-1になる可能性がある理由であり、4つの8ビット-1の組み合わせはint -1になります)

したがって、ヘッダーファイル#include <string.h>を追加します

HDU4864タスク

トピック:

説明

今日、会社にはmのタスクを完了する必要があります。i番目のタスクは、完了するのにxi分かかります。一方、このタスクの難易度はyiです。このタスクのレベルyiより下のレベルのマシンはこのタスクを完了できません。会社がこのタスクを完了すると、(500 * xi + 2 * yi)ドルがもらえます。 
同社にはn台のマシンがあります。各マシンには、最大作業時間とレベルがあります。タスクの時間がマシンの最大稼働時間を超える場合、マシンはこのタスクを完了できません。各マシンは1日でしかタスクを完了できません。各タスクは1台のマシンでのみ完了できます。 
同社は、今日完了できるタスクの数を最大化することを望んでいます。複数の解決策がある場合、彼らはお金を最大にすることを望んでいます。

入力

入力にはいくつかのテストケースが含まれています。 
最初の行には、2つの整数NとMが含まれています。Nはマシンの数です。Mはタスクの数です(1 <= N <= 100000,1 <= M <= 100000)。 
次のN行には、それぞれ2つの整数xi(0 <xi <1440)が含まれています。yi(0 = <yi <= 100).xiはマシンが動作できる最大時間です。yiはマシンのレベルです。 
次のM行には、それぞれ2つの整数xi(0 <xi <1440)が含まれています。yi(0 = <yi <= 100).xiはタスクを完了するために必要な時間です。yiはタスクのレベルです。

出力

テストケースごとに、2つの整数、会社が今日完了することができるタスクの最大数、およびそれらが得るお金を出力します。

サンプル入力

1 2
100 3
100 2
100 1

サンプル出力

1 50004

同様のトピックがあります:HDU 3466誇り高き商人(バックパック)https://blog.csdn.net/nameofcsdn/article/details/52093195

私の考えは、n台のマシンとm台のジョブタスクを降順で配置することですが、

マシンは、yを最初のキーワードとして降順で配置し、xを最初のキーワードとして降順で配置します。

ジョブは、xを最初のキーワード、yを2番目のキーワードとして降順で並べ替えられます。つまり、500 * x + 2 * yに従って並べ替えられます。

欲張り戦略は次のとおりです。

機械を大きいものから小さいものへと配置します。配置できない場合は、配置しないでください。

ジョブごとに、ジョブ以上の最小のマシンを選択します。(最小のマシンは何ですか、それは上記の順序で直接与えられます)

コード:

#include<iostream>
#include<string.h>
#include<algorithm>
using namespace std;
 
struct node
{
	int x;
	int y;
};
 
node noden[100005];
node nodem[100005];
int list[100005];	//list不为0表示对应的机器已经被选择
int section[101];	//section[i]是满足y不小于i的机器一共有多少个(对应最大下标)
 
bool cmpy(node a, node b)	//机器以y为第一关键字降序排列,以x为第一关键字降序排列
{
	if (a.y > b.y)return true;
	if (a.y < b.y)return false;
	return a.x>b.x;
}
 
bool cmpx(node a, node b)		//Task以x为第一关键字降序排列,以y为第二关键字降序排列
{
	if (a.x > b.x)return true;
	if (a.x < b.x)return false;
	return a.y>b.y;
}
 
int main()
{
	int n, m;
	while (cin >> n >> m)
	{
		for (int i = 0; i < n; i++)cin >> noden[i].x >> noden[i].y;
		for (int i = 0; i < m; i++)cin >> nodem[i].x >> nodem[i].y;
		sort(noden, noden + n, cmpy);	//机器
		sort(nodem, nodem + m, cmpx);	//Task
		memset(list, 0, sizeof(list));
		memset(section, 0, sizeof(section));
		for (int i = 0; i < n; i++)section[noden[i].y] = i + 1;
		for (int i = 99; i >= 0; i--)if (section[i] < section[i + 1])section[i] = section[i + 1];
		long long num = 0, sum = 0, t;
		for (int i = 0; i < m; i++)
		{
			t = nodem[i].y;
			for (int j = section[t] - 1; j >= 0; j--)
			{
				if (list[j] || noden[j].x < nodem[i].x)continue;
				list[j] = 1;
				num++;
				sum += nodem[i].x * 500 + t * 2;
				break;
			}
		}
		cout << num << " " << sum << endl;
	}
	return 0;
}

このコードはACですが、1326msは十分な速度ではありません。

 

以下では、この問題の本質を説明し、次にアルゴリズムを最適化します。

まず、割り当てをこのように並べ替える必要があることは間違いありません。つまり、500 * x + 2 * yが最大の割り当てが優先されます。

つまり、データが何であれ、500 * x + 2 * yのサイズのジョブに適切なマシンが優先される限り、最適なソリューションが得られます。

あと1つだけ質問があります。仕事に最適な機械を選ぶには?

欲張り戦略は、ジョブが候補マシンセット内の複数の要素である場合にすぎません。次にスケジュールされるジョブが影響を受けないように、要素を選択してみてください。このようにスケジュールされたジョブの数が最大になります。

 

したがって、このような小さなモデルから問題を抽出できます。

2つのジョブと2つのマシンがあります。ジョブAはマシン1とマシン2を選択でき、ジョブBはマシン1のみを選択できます。

しかし、Aはマシンを優先するため、どのマシンがマシン1で、どのマシンがマシン2であるかを正確かつ効率的に判断できる戦略が必要ですか?

ジョブAのマシンを配置する際、Bの詳細情報がなかったため、現時点では、ジョブBがマシン1しか選択できないことはわかりません。

モデルの解決策:

Aはマシン2を選択できますが、Bは選択できないため、Bのyがマシン2のyよりも大きいことを意味します(明らかに、BのxはAのxを超えず、Aのxはマシン2のxを超えません)

したがって、マシン1のyはBのy以上であり、Bのyはマシン2のyよりも大きい、つまり、マシン2のyはマシン1のyよりも小さいことがわかります。

(宿題Bはマシン1しか選べないという条件を使ったと思うかもしれませんが、実は詳しく説明していません。

注意深く区別すると、私の解決策には問題がないことがわかります。少なくとも、この独立したモデルについては、私の結論が正しいことは明らかです)

モデルの回答:

Aに機械を配置する場合は、yが小さいものを選択してください。

 

この質問に戻ります。

欲張り戦略は実際には非常に単純であり、上記のようにする必要はありません。

ジョブごとにマシンを配置するときは、yが最も小さいマシンを選択します。複数ある場合は、1つを選択します。

上記のモデルで、私がいくつかの場所でより大きく、いくつかの場所で以上または等しいと書いたことを発見したかもしれません。

上記のモデルは問題がないだけでなく、非常に正確であると確信しています。

つまり、上記のモデルを注意深く理解すれば、yが最小のマシンが複数ある場合に1台を選択できる理由を理解できます。

コード:

#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;
 
struct node
{
	int x;
	int y;
};
 
node noden[100005];
node nodem[100005];
vector<int>v[101];
 
bool cmp(node a, node b)		//Task以x为第一关键字降序排列,以y为第二关键字降序排列
{
	if (a.x > b.x)return true;
	if (a.x < b.x)return false;
	return a.y>b.y;
}
 
int main()
{
	ios_base::sync_with_stdio(false);
	int n, m;
	vector< int >::iterator p;
	while (cin >> n >> m)
	{
		for (int i = 0; i <= 100; i++)v[i].clear();
		for (int i = 0; i < n; i++)
		{
			cin >> noden[i].x >> noden[i].y;
			v[noden[i].y].insert(v[noden[i].y].end(), i);
		}
		for (int i = 0; i < m; i++)cin >> nodem[i].x >> nodem[i].y;
		sort(nodem, nodem + m, cmp);	//Task
		long long num = 0, sum = 0;
		for (int i = 0; i < m; i++)
		{
			bool b = false;
			for (int j = nodem[i].y; j <= 100; j++)
			{
				if (b)break;
				for (p = v[j].begin(); p != v[j].end(); p++)
				{
					if (noden[*p].x >= nodem[i].x)
					{
						num++;
						sum += nodem[i].x * 500 + nodem[i].y * 2;
						v[j].erase(p);
						b = true;
						break;
					}
				}
			}
		}
		cout << num << " " << sum << endl;
	}
	return 0;
}

HDU1051木の棒

トピック:

 

n本の木の棒の山があります。各スティックの長さと重さは事前にわかっています。スティックは木工機械で一本一本加工されます。機械がスティックの処理を準備するには、セットアップ時間と呼ばれる時間が必要です。セットアップ時間は、機械の洗浄操作と工具や形状の変更に関連しています。木工機械のセットアップ時間は次のとおりです。 

(a)最初の木製スティックのセットアップ時間は1分です。 
(b)長さlおよび重量wのスティックを処理した直後、l <= l 'およびw <= w'の場合、機械は長さl 'および重量w'のスティックのセットアップ時間を必要としません。それ以外の場合は、セットアップに1分かかります。 

あなたはn本の木の棒の与えられた山を処理するための最小のセットアップ時間を見つけることです。たとえば、長さと重量のペアが(4,9)、(5,2)、(2,1)、(3,5)、および(1,4)である5本のスティックがある場合、最小設定ペア(1,4)、(3,5)、(4,9)、(2,1)、(5,2)のシーケンスがあるため、時間は2分である必要があります。 

入力

入力はT検定ケースで構成されます。テストケースの数(T)は、入力ファイルの最初の行に示されています。各テストケースは2行で構成されます。最初の行にはテストケース内の木の棒の数を表す整数n、1 <= n <= 5000があり、2番目の行にはn 2個の正の整数l1、w1、l2が含まれています。 、w2、...、ln、wn、それぞれの大きさは最大10000です。ここで、liとwiは、それぞれi番目の木の棒の長さと重さです。2n個の整数は、1つ以上のスペースで区切られます。 

出力

出力には、1行に1つずつ、分単位の最小セットアップ時間が含まれている必要があります。 

サンプル入力

3 
5 
4 9 5 2 2 1 3 5 1 4 
3 
2 2 1 1 2 2 
3 
1 3 2 2 3 1

サンプル出力

2 
1 
3

アイデア:最初に並べ替えてから、貪欲になります

コード:

#include<iostream>
#include<algorithm>
using namespace std;
 
struct nod
{
	int l, w;
	bool visit;
}node[5001];
 
bool cmp(nod a, nod b)
{
	if (a.l == b.l)return a.w<b.w;
	return a.l < b.l;
}
 
int main()
{
	int T, n, ans;
	cin >> T;
	while (T--)
	{
		cin >> n;
		for (int i = 1; i <= n; i++)
		{
			cin >> node[i].l >> node[i].w;
			node[i].visit = false;
		}
		sort(node + 1, node + n + 1, cmp);
		ans = 0;
		for (int i = 1; i <= n; i++)
		{
			if (node[i].visit)continue;
			ans++;
			int k = i;
			for (int j = i + 1; j <= n; j++)
			{
				if (node[j].visit)continue;
				if (node[k].w <= node[j].w)node[j].visit = true, k = j;
			}
		}
		cout << ans << endl;
	}
	return 0;
}

 

おすすめ

転載: blog.csdn.net/nameofcsdn/article/details/112750825