月度開銷
題目傳送門
思路
給定連續N天的開銷,需要將這些天分成M個財政週期,使得開銷最多的財政週期的開銷儘可能少。
首先,我們可以確定一個財政週期的長度l,即將N天平均分成M個財政週期。這樣每個財政週期的長度就是N/M。
然後,我們需要計算每個財政週期中的開銷總和。假設當前財政週期的起始位置為i,那麼當前財政週期的開銷總和就是從第i天開始的連續N/M天的開銷之和,即SUM(i, i+N/M-1)。
接下來,我們需要找到開銷總和最大的財政週期,即找到使得SUM(i, i+N/M-1)最大的i。
我們可以使用滑動視窗的思想來解決這個問題。首先,我們計算起始位置為0的財政週期的開銷總和SUM(0, N/M-1)。然後,我們從左往右依次移動起始位置i,每次移動一個位置,將起始位置i-1的開銷減去第i-1天的開銷,再加上第i+N/M-1天的開銷,即得到起始位置為i的財政週期的開銷總和SUM(i, i+N/M-1)。我們將SUM(i, i+N/M-1)與原先的最大開銷進行比較,如果大於最大開銷,就更新最大開銷。
最後,最大開銷就是最大月度開銷的最小值。
#include <bits/stdc++.h>
using namespace std;
#define N 100005
int n, m, a[N];
bool check(int k) //在每個子段和小於等於k的情況下,最少的子段數量是否小於等於m
{//注意:a中的單獨一個元素也可能大於k
int sum = 0, ct = 1;//sum:當前子段加和 ct:現在在看第幾個子段
for(int i = 1; i <= n; ++i)
{
if(a[i] > k)//存在元素大於k,
return false;
if(sum + a[i] <= k)
sum += a[i];
else
{
ct++;//看下一子段
sum = a[i];//i作為下一子段的第一個元素
}
}
return ct <= m;
}
int main()
{
int tot = 0;//加和
cin >> n >> m;
for(int i = 1; i <= n; ++i)
{
cin >> a[i];
tot += a[i];
}
int l = 0, r = tot, mid;//子段和最大值不會大過所有數的加和
while(l < r)//二分答案求滿足某一條件的最小值
{
mid = (l + r) / 2;
if(check(mid))
r = mid;
else
l = mid + 1;
}
cout << l;
return 0;
}