POJ 3069 Saruman's Army
阿新 • • 發佈:2021-01-11
題目如下:
一個遊戲:在一條直線上有N個糖果。第i個糖果的位置是X[i]。從這N個糖果中選擇若干個,把他們標記起來。對於每一個糖果,在和它本身相距為R的區域內必須要有標記的糖果(本身帶有標記的糖果,就可以認為和它相距為0的地方有一個糖果被標記)。在滿足這個條件的情況,最後如果有a個糖果被標記,編寫程式使a最小化。
Input
輸入的測試檔案將包含多個樣例。 每個測試樣例第一行有兩個資料,整數R(其中0≤R≤1000)和整數N(其中1≤N≤1000)。 下一行包含N個整數,指示每個糖果的位置X[1],…,X[N](其中0≤X[i]≤1000)。當RN-1時,輸入結束。
Output
對於每組輸入資料,輸出一個數,代表a的最小值。
Sample Input
0 3
10 20 20
10 7
70 30 1 7 15 20 50
-1 -1
Sample Output
2
4
解題思路:
這道題是經典的區間貪心問題,題目中給的每一個點都有其相應的範圍[x-R,x+R]。並且,在每個點距離為R的區域裡必須有一個帶有標記的點。(包括該點本身也可以進行標記)在滿足這種條件的情況下,希望能為儘可能少的點去新增標記,求最少可標記的點。
那麼,根據這道題,我們應該如何制定我們的貪心策略呢?
首先,既然是要求儘可能少的點去新增標記。我們假設:從最左邊考慮,當前點為x、要新增標記的點為y。那麼y一定是在當前點x所引出範圍中更靠右的點。因為y只有越靠右,該範圍能新增標記的點也就越少。越靠左,該範圍能新增標記的點也就越多。不懂?再提個假設:共有點:1、3、4,當前點為1,範圍為5。那麼,當前點的範圍就是[-4,6]。既然我們想讓這個範圍中新增標記的點儘可能少,那麼這個新增標記的點就是4而不是3。因為,我們是從左往右進行考慮的,假設我們將3添加了標記,那麼4肯定也能新增標記。這樣的話,當前範圍中新增標記的點就不是儘可能少的了。
這樣的話,我們的大致思路就確定下來了。由於我們是從左往右進行考慮的,且測試例項中的輸入點可能不是從左往右依次有序的。所以在進行標記點之前,我們應該把輸入的點進行從小到大排序。(這裡用sort函式就行了)
排完序之後,我們應該從左往右進行檢視。對於每一個檢視的點,我們都應該找出該點範圍內距離最靠右的點。只有這樣的話,才能保證每個範圍內標記的點必須只有一個。
但是,光上面的結論還不足以完成這道題目。設想一下,如果有的點已經是標記的點了,那麼我們還用去找這個點所引出的區間中最靠右的點了嗎?由於之前已經說過,在每個點距離為R的區域裡必須有一個帶有標記的點。(包括該點本身)所以,如果有的點已經被標記了,那麼就不用去找該點所引出的區間中最靠右的點了。而應該跳過這個區間,去找下一個待標記的點。
程式碼如下:
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;
int R, N; //定義整數R和整數N
int X[1001]; //定義儲存每個點的陣列
void greedy(); //定義貪心演算法的函式
void greedy() {
int i=0;
int sum = 0; //代表插入點的個數
int s, p; //定義起始點和插入點的變數
while (i < N) {
s = X[i++]; //將起始點進行賦值(可以將i++換成i,只不過後面迴圈的次數+1了而已)
while(i<N && X[i] <= s + R) { //一直向右前進,直到距s的距離大於R的點(i<N的原因就是為了防止下標越界)(如果將i<N去掉的話,某些輸入例項就會越界)(可以參考如下例項:R=10 N=4 {1,2,3,4})
i++;
}
p = X[i - 1]; //代表設定標記點
while (i<N && X[i] <= p + R) { //標記點所引出的區間應當跳過(繼續向右前進,直到距p的距離大於R的點)
i++;
}
sum++;
}
cout << sum << endl;
}
int main() {
int i;
while (scanf("%d %d", &R, &N) != EOF) {
if (R ==-1 && N==-1) {
return 0;
}
else {
for (i = 0; i < N; i++) {
scanf("%d", &X[i]);
}
sort(X, X + N); //將輸入的數進行從小到大排序。
greedy();
}
}
}