裁剪序列Cut the Sequence

wlesq發表於2024-06-08

image
首先,我們可以先想一想樸素演算法,推出DP,i表示分了幾段,則可以推出$$F[i]=min_{1<=j<=i}(f[j]+max_{j+1<=k<=i}(a[k]))$$

點選檢視程式碼
	memset(f,0x3f,sizeof f);
	f[0]=0;
	for(int i=1;i<=n;i++)
	{
		for(int j=0;j<i;j++)
		{
			int tmp=0;ll sum=0;
			for(int k=j+1;k<=i;k++)
			{
				tmp=max<ll>(tmp,a[k]);
				sum+=a[k];
			}
			if(sum<=m)f[i]=min(f[i],f[j]+tmp);
		}
	}
	cout<<f[n];

這是\(O(n^2)\)的演算法複雜度,如何最佳化,我們可以想到用雙端佇列維護\(a\)陣列,以單調遞減趨勢
用一個變數\(pos\)控制他的左邊界別超出\(m\),如何維護最優值呢?
顯然\(F[i]\)成一個單調遞增趨勢,一個區間\(a\)的值越大,他包含的範圍應該越大,當max(a[j+1—>i])的值固定,那麼j越小越好,當不固定時,可以排除一些情況,即$$j1<j2 a[j1]<=a[j2]是無用的$$
這是我們可以看出用單調佇列維護由於f[j]+max(a[j+1—>i])不單調,要用multiset維護一下
與單調佇列同步,具體詳細看程式碼
\(F[i]=f[pos-1]+a[q.front()]\)

點選檢視程式碼
#include <bits/stdc++.h>
#define ll long long
#define mk make_pair 
#define pb push_back
#define lid (rt<<1)
#define rid (rt<<1|1)
#define speed() ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
using namespace std;
const int N = 1e5+5;
ll n,m,a[N],f[N];
multiset <ll> s;
deque <ll> q;
int main()
{
	speed();
	cin>>n>>m;
	for(int i=1;i<=n;i++)
	{
		cin>>a[i];if(a[i]>m)return cout<<-1,0;
	}
	deque <int> q;
	int ans=0,pos=1;
	for(int i=1;i<=n;i++)
	{
		ans+=a[i];
		while(ans>m)ans-=a[pos++];	//邊界
		while(q.size()&&q.front()<pos)
		{
			ll tp=f[q.front()];
			q.pop_front();//因為單調佇列單調遞減的緣故,所以front後一個就是max
			if(q.size())s.erase(tp+a[q.front()]);//同步
		}
		while(q.size()&&a[q.back()]<=a[i])
		{
			ll tp=a[q.back()];
			q.pop_back();
			if(q.size())s.erase(tp+f[q.back()]);
		}
		if(q.size())s.insert(a[i]+f[q.back()]);//佇列中每兩個相鄰元素就得insert一次
		q.push_back(i);
		f[i]=f[pos-1]+a[q.front()];//一種可能情況,選擇pos->i整個區間,因為該段區間最大值(a[q.front()])與f[k-1]也構成一對可能解
		if(s.size())f[i]=min<ll>(f[i],*s.begin());
		
	}
	cout<<f[n];
	return 0;
 } 

相關文章