1. 程式人生 > 其它 >【YBTOJ】【單調佇列優化DP】出題方案

【YBTOJ】【單調佇列優化DP】出題方案

出題方案

現在小澤的手上有 \(n\) 道難題,編號分別為 \(1\sim n\) ,第 \(i\) 道題的難度係數是 \(a_i\)

小澤想用這些題出比賽,他會把題目按照編號劃分為若干個非空連續區間,每個區間對應了一場比賽。

特別的,如果某場比賽的題目難度係數之和超過了給定的常數 \(m\) ,這場比賽會過於毒瘤,所以他不希望出現這樣的情況。

定義每場比賽的難度係數為這場比賽所有題目難度係數的最大值。

小澤想讓你找出一組合法方案,使得所有比賽的難度係數之和最小。

\(n\leq3\times10^5\)\(1\leq a_i\le10^6\)\(1\leq m \leq 1.1\times10^9\)

題解

靜下心來想這道題吧!

\(dp_i\) 表示把前 \(i\) 個數分段後最小代價。

則樸素 dp : \(dp_i=\min\{dp_j+\max\limits_{k=j+1}^i a_k\}\) 。複雜度\(O(n^2)\)

考慮優化:

  • 因為後一項和範圍內 \(\max\) 值有關,我們考慮使用一個單調佇列,維護 \([j+1,i]\) 區間內 \(a_k\) 的最大值。
    維護一個最大值的單調佇列,那麼顯然這個單調佇列是單調不增的。
  • 假設此佇列中的值為\((k_1,k_2,\dots,k_m)\)(按序號順序排序)。
    考慮相鄰的兩個元素 \(k_i\)\(k_{i+1}\)

    \(j\in[k_{i},k_{i+1})\) 內的 \(dp_j+\max\limits_{k=j+1}^i a_k\) 中,後面那一項是相同的,為 \(a_{k[i+1]}\)
  • 考慮使 \(dp_j\) 最小。
    顯然 \(dp_i\) 是單調不減的,那麼一定當 \(j\) 取值 \(k_i\) 時, dp 值取最小。
  • 對於佇列內的多個元素,則需要對每對相鄰元素都求其對應的 \(dp+\max a\) , 利用資料結構來快速找到這些值中最小的那一個,即為 \(dp_i\)
  • 最後,考慮原 \(dp\) 式中 \(j\) 的取值範圍。顯然 \(j\) 滿足 \(\sum\limits _{k=j+1}^i \le m\)
    , 且 \(j< i\)

概述一下:

  • 對於每個 \(i\) ,求出第一個滿足條件的 \(j\) 。維護一個 \([j,i)\) 的單調佇列。
  • 求出單調佇列中每相鄰一對的答案,用資料結構維護,其最小值計入 \(dp_i\)

一些注意事項

  1. 關於資料結構:要求支援動態插入、刪除、查詢最值,可以使用 STL 內建的 multiset 或手寫平衡樹
  2. 關於佇列:要支援查詢此時佇列中的每個元素, STL 內建的 deque 無法滿足要求。我們可以使用單純陣列模擬自行封裝。
  3. 關於資料結構的查詢:在佇列長度 \(\ge 2\) 時才有意義。(否則應該會RE罷)

程式碼

#include <bits/stdc++.h>
#define fo(a) freopen(a".in","r",stdin),freopen(a".out","w",stdout);
using namespace std;
const int INF = 0x3f3f3f3f,N = 3e5+5;
typedef long long ll;
typedef unsigned long long ull;
inline ll read(){
	ll ret=0;char ch=' ',c=getchar();
	while(!(c>='0'&&c<='9'))ch=c,c=getchar();
	while(c>='0'&&c<='9')ret=(ret<<1)+(ret<<3)+c-'0',c=getchar();
	return ch=='-'?-ret:ret;
}
template <typename T> struct que{
	T a[N]; int st=1,ed=0;
	void dque(){st=1,ed=0;}
	inline void clear(){st=1,ed=0;}
	inline int size(){return ed-st+1;}
	inline bool empty(){return !(ed-st+1);}
	inline void pop_front(){st++;}
	inline void pop_back(){ed--;}
	inline T front(){return a[st];}
	inline T back(){return a[ed];}
	inline void push_back(T x){a[++ed] = x;}
	inline T operator [] (int x){return a[st+x-1];}
};
int n;ll m;
ll dp[N],a[N],sm[N];
inline ll sum(int l,int r){return l>r ? -1ll*INF*INF : sm[r]-sm[l-1];}
typedef multiset<ll>::iterator itl;
multiset<ll> s;
que<int>q;
signed main(){
	memset(dp,0x3f,sizeof(dp));
	n = read() , m = read();
	for(int i = 1 ; i <= n ; i ++)
		a[i] = read() , sm[i] = sm[i-1] + a[i];
	
	dp[0] = 0;
	int j = 0; q.push_back(0);
	
	for(int i = 1 ; i <= n ; i ++){
		while(sum(j+1,i) > m) j++;
		while(!q.empty() && q.front() < j) {
			if(q.size() >= 2) s.erase(s.find(dp[q[1]]+a[q[2]]));
			q.pop_front();
		}
		while(!q.empty() && a[q.back()] < a[i]){
			if(q.size() >= 2) s.erase(s.find(dp[q[q.size()-1]] + a[q[q.size()]]));
			q.pop_back();
		}
		if(!q.empty()) s.insert(dp[q.back()] + a[i]);
		q.push_back(i);
		
		dp[i] = dp[j] + a[q.front()];
		if(q.size() >= 2) dp[i] = min(dp[i],*s.begin());
	}
	printf("%lld",dp[n]);
	return 0;
}