【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\)

复杂度\(O(n\log n)\)

一些注意事项

  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];}
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;
}
posted @ 2021-09-16 16:08  Last-Order  阅读(81)  评论(0编辑  收藏  举报