الگوها در برنامه نویسی پویا


سلب مسئولیت: روشی که در زیر توضیح داده شده است جهانی نیست، اما اغلب می تواند یک مشکل را حل کند یا به شما کمک کند تا راه حل مناسب را پیدا کنید.

اگر مشکل به این واقعیت ختم شود که لازم است آرایه را به صورت بهینه به زیربخش های غیر متقاطع (دنباله ای از عناصر متوالی) تقسیم کنیم (یا تعداد تقسیم های مناسب را بشماریم)، ​​پس ارزش تلاش برای حل آن را دارد. با استفاده از برنامه نویسی پویا.

یک نمونه طرح راه حل به شرح زیر است:
dp[i] - پاسخ برای اولین عناصر i

شمارش dp[i]: از آنجایی که ما فقط اولین عناصر i را در نظر می گیریم، عنصر i-امین آخرین عنصر خواهد بود، به این معنی که این عنصر در آخرین زیربخش و در عین حال، سمت راست ترین عنصر در آنجا خواهد بود. بنابراین، می‌توانیم روی مرز سمت چپ آخرین زیربخش j تکرار کنیم. در فرآیند شمارش، مقدار این زیربخش را محاسبه می کنیم و اگر درست باشد، dp[i] را تا dp[j - 1] و مقدار زیربخش [j;i] را دوباره محاسبه می کنیم.

مشکل ساده زیر را در نظر بگیرید: با توجه به آرایه ای از اعداد صحیح، باید آن را به حداقل تعداد زیربخش های غیر متقاطع تقسیم کنید تا هر عدد در زیربخشی گنجانده شود و هر زیربخش دارای اعداد یکسان باشد. به عنوان مثال، برای یک آرایه 1 2 2 3 3 3 2 1 1، پارتیشن بهینه به صورت زیر است: [1] [2 2] [3 3 3] [2] [1 1]. این کار به سادگی با عبور از آرایه به راحتی حل می شود (همه عناصر متوالی مشابه را در یک زیربخش قرار می دهیم)، اما برای مثال آن را با استفاده از برنامه نویسی پویا حل می کنیم.
  intn; cin>> n // آرایه را با 1-شاخص پر کنید vector arr(n + 1); برای (int i = 1; i <= n; i++) cin>> arr[i]; // ابتدا +oo را روی مقادیر dp تنظیم کرد vector dp(n + 1, 1000000000); // آرایه ای با طول صفر نیازی به تقسیم ندارد، بنابراین پاسخ آن 0 است dp[0] = 0; // پاسخ dp[i] را در i صعودی بشمارید برای (int i = 1; i <= n; i++) { // در حال حاضر arr[i] آخرین عنصر است، بنابراین سمت راست ترین عدد در آخرین زیربخش خواهد بود. // همه گزینه ها را برای جایی که آخرین زیربخش شروع شده است، حلقه بزنید برای (int j = i; j > 0; j--) { if (arr[j] != arr[i]) { // اگر عنصری را ملاقات کنید که با آخرین عنصر برابر نیست، آن زیربخش شامل اعداد مختلفی خواهد بود و این با شرط مطابقت ندارد // هیچ فایده ای برای ادامه وجود ندارد، زیرا با جابجایی مرز سمت چپ به چپ، این عنصر ناپدید نمی شود، بنابراین ما شکسته می شویم زنگ تفريح؛ } // تصور کنید آخرین زیربخش [j;i] بود // بنابراین باید پارتیشن بهینه اولین عناصر j-1 را بگیرید و 1 را اضافه کنید (خود زیربخش [j; i]) dp[i] = min(dp[i]، dp[j - 1] + 1); } } cout << dp[n];
اگر ممکن است عناصر به هیچ یک از زیربخش ها تعلق نداشته باشند، فقط باید گزینه مناسب را در نظر بگیرید، به عنوان dp[i] = dp[i - 1]

اگر لازم است آرایه را دقیقاً به k زیربخش تقسیم کنیم، پارامتر دوم به سادگی در برنامه نویسی پویا اضافه می شود - به چند بخش تقسیم می شود.
یعنی اکنون dp زیر را در نظر می گیریم:
dp[i][j] پاسخ اولین عناصر i است، اگر آنها را دقیقاً به قطعات j تقسیم کنیم.
مراقب حالت های نامعتبر باشید.

محاسبه مجدد دینامیک یکسان است، اما با در نظر گرفتن پارامتر دوم. یعنی با شمارش dp[i][k] و مرتب‌سازی از طریق مرز سمت چپ آخرین زیربخش j، dp[i][k] را تا dp[j - 1][k - 1] و مقدار قطعه را دوباره محاسبه می‌کنیم. [j;i].

سلب مسئولیت: روشی که در زیر توضیح داده شده است جهانی نیست، اما اغلب می تواند یک مشکل را حل کند یا به شما کمک کند تا راه حل مناسب را پیدا کنید.

اگر مجموعه‌ای از شکاف‌ها روی یک محور (معمولاً محور زمانی یا شاخص‌های یک آرایه) قرار دارند و باید برخی از آنها را به‌طور بهینه انتخاب کنید تا شکاف‌های انتخاب‌شده با هم تلاقی نکنند، باید از برنامه‌نویسی پویا استفاده کنید. .

طرح حل تقریبی:

در ابتدا، شکاف های موجود را بر اساس حاشیه سمت راست مرتب می کنیم. بیایید دینامیک زیر را شروع کنیم: dp[i] - پاسخ اولین فواصل i. 
ما به صورت زیر دوباره محاسبه می کنیم: ابتدا وضعیتی را در نظر بگیرید که این بازه استفاده نمی شود، سپس فقط dp[i] = dp[i-1]. توجه داشته باشید که این تضمین می کند که مقادیر dp[i] با رشد i کاهش نمی یابد. و این منطقی است، زیرا. با اضافه کردن یک شکاف جدید، نمی‌توانیم پاسخ جهانی را بدتر کنیم: یا به سادگی شکاف جدید را نادیده می‌گیریم، یا با استفاده از آن یک نوع سودآورتر می‌سازیم. حال، اگر بخواهیم از شکاف i-ام استفاده کنیم، می‌توانیم از آن شکاف‌هایی استفاده کنیم که مرزهای سمت راست آن‌ها کمتر از مرز چپ شکاف فعلی است، زیرا باید مجموعه‌ای از شکاف‌های غیر همپوشانی را انتخاب کنیم. برای انجام این کار، ابتدا شکاف ها را بر اساس حاشیه سمت راست مرتب کردیم تا اکنون بتوانیم موقعیت مورد نیاز را به طور موثر پیدا کنیم. در صورت امکان می توان این کار را به صورت تحلیلی انجام داد، اما در حالت کلی می توان با جستجوی بینایی شکافی را یافت که مرز سمت راست آن کمتر از مرز سمت چپ فعلی و در عین حال حداکثر ممکن است. یکی ما می خواهیم مرز مناسب را به دلایل حریصانه به حداکثر برسانیم، زیرا همانطور که من رشد می کنم، پاسخ فقط می تواند افزایش یابد. بر این اساس، موقعیت p مورد نیاز را پیدا کرده و dp[i] را تا dp[p] و بازه i-ام دوباره محاسبه می‌کنیم.