n عدد جواهر داریم که جواهر i-ام دارای ارزش
و وزن
است. می خواهیم زیرمجموعه ی k عضوی
را از این جواهرها را انتخاب کنیم به صورتی که عبارت زیر بیشنه شود:
قیود مساله:



گمان کنم این مساله یکی از آن مسائلی باشد که برای بسیاری از افراد شدیدا آموزنده باشد و آن ها را با روش جدیدی برای مواجه شدن با برخی مسائل آشنا کند.
راه حل من برای حل این مساله دو گام دارد:
فرض کنید روشی داریم که با آن می توانیم با آن می توانیم بدست بیاوریم که آیا زير مجموعه ی k تایی از جواهرها وجود دارد که عبارت فوق برای آن ها بیشتر از مقدار مفروض x است یا نه. اسم این تابع را test می گذاریم (بعدا به این تابع خواهیم پرداخت):
bool test(int x);
در این صورت می توانیم با استفاده از این تابع و جستجوی دودوئی بیشترین مقدار را برای عبارت بالا پیدا کنیم:
double Left = 0; double Right = 1000000; double Result = 0; while(fabs(Left - Right) > 1e-6) { double Mid = (Left + Right) / 2; if(test(Mid)) { Result = Mid; Left = Mid; } else Right = Mid; }
اکنون به توضیح اینکه چگونه می توانیم بفهمیم که “آیا زير مجموعه ی k تایی از جواهرها وجود دارد که عبارت فوق برای آن ها بیشتر از مقدار مفروض x است یا نه”. در واقع باید زیر مجموعه از جواهرات وجود داشته باشد که در نامساوی زیر صدق کند:

اگر x را به سمت چپ معادله ببریم:

و چون همواره داریم
، پس نتیجه می گیریم که زیرمجموعه ی موردنظر باید در عبارت زیر صدق کند:

و برای اینکه بفهمیم آیا زير مجموعه ای وجود دارد که در این رابطه صدق می کند، بیشترین مقدار را برای
پیدا می کنیم. اگر این بیشترین مقدار بزرگتر از صفر بود، چنین زیر مجموعه ای وجود دارد و گرنه وجود ندارد.
برای اینکه بیشینه ی این عبارت را پیدا کنیم، کافی است که
را برای هر کدام از جواهرها حساب کنیم و k عنصر بزرگتر آرایه ی حاصل را انتخاب کنیم و آن ها را با هم جمع کنیم.
پس کد تابع تست به این صورت خواهد بود:
bool test(double x) { for(int i = 0; i < n; i ++) arr[i] = v[i] - x * w[i]; sort(arr, arr + n); double sum = 0; for(int i = 0; i < k ;i ++) sum += arr[n-1-i]; if(sum >= 0) return true; else return false; }
خُب! گمان کنم اگر بخش قبل را خوب فهمیده باشید، انجام دادن این گام برایتان به آسانی خوردن یک سیب باشد!
در واقع تابعی نیاز داریم که برای مقدار x زیرمجموعه ای از اشیاء انتخاب کند که عبارتی که قبلا درباره ی آن بحث کردیم درباره ی آن زیرمجموعه صدق کند. در اینجا نیز باید مانند بالا جواهرها را بر حسب
مرتب کنیم و k تا بزرگترین را انتخاب کنیم. تابع construct همین کار را انجام می دهد:
vector<int> construct(double x) { vector<P> arr(n); for(int i = 0; i < n; i += 1) arr[i] = P(v[i] - x * w[i], i); sort(arr.begin(), arr.end()); reverse(arr.begin(), arr.end()); vector<int> result(k); for(int i = 0; i < k; i ++) result[i] = arr[i].second+1; return result; }
#include <map> #include <iostream> #include <string> #include <cstdio> #include <cmath> #include <vector> #include <algorithm> using namespace std; #define P pair<double, int> #define eps 1e-7 int n, v[200000], w[200000], k; double arr[200000]; int check(double x) { for(int i = 0; i < n; i ++) arr[i] = v[i] - x * w[i]; sort(arr, arr + n); double sum = 0; for(int i = 0; i < k ;i ++) { sum += arr[n-1-i]; if(sum < -eps) return false; } if(sum >= -eps) return true; else return false; } vector<int> construct(double x) { vector<P> arr(n); for(int i = 0; i < n; i += 1) arr[i] = P(v[i] - x * w[i], i); sort(arr.begin(), arr.end()); reverse(arr.begin(), arr.end()); vector<int> result(k); for(int i = 0; i < k; i ++) result[i] = arr[i].second+1; return result; } int main() { while(scanf("%d %d", &n, &k) == 2) { for(int i = 0; i < n; i ++) scanf("%d %d", v + i, w + i); double Left = 0, Right = 10000000; double result = 0; while(fabs(Left - Right) > 1e-6) { double Mid = (Left + Right) / 2; if(check(Mid)) { result = Mid; Left = Mid; } else Right = Mid; } vector<int> vresult = construct(result); sort(vresult.begin(), vresult.end()); for(int i = 0; i < vresult.size(); i ++) { if(i) cout << " "; cout << vresult[i]; } cout << endl; } return 0; }