【ybtoj】【单调队列】出题方案

题意

现在小泽的手上有 n 道难题,编号分别为 1~n ,第 i 道题的难度系数是 ai 。
小泽想用这些题出比赛,他会把题目按照编号划分为若干个非空连续区间,每个区间对应了一场比赛。
特别的,如果某场比赛的题目难度系数之和超过了给定的常数 m ,这场比赛会过于毒瘤,所以他不希望出现这样的情况。
定义每场比赛的难度系数为这场比赛所有题目难度系数的最大值。
小泽想让你找出一组合法方案,使得所有比赛的难度系数之和最小。
\(n\)\(3×10^5\)\(1\)\(a_i\)\(10^6\)\(1\)\(m\)\(1.1×10^9\)

题解

个人觉得思维难度非常大的一道题,卡了我很久。
考虑朴素 DP: \(dp_i=\min\{dp_j\}+\max a_k\).
然而这里后面的 \(\max a_k\)不独立,而是还与前面的 \(j\) 有关,所以不能简单地用单调队列优化。
先假设对 \(a_k\) 做单调队列维护最大值,很好维护何时出队,入队,但是此时仍然不知道 \(j\) 取何值时为 \(dp_i\) 的转移点。
考虑对单调队列中任何一个 \(q_j\),由于单调队列内部单调递减,那么紧接着 \(q_j\) 的下一个元素 \(q_{j+1}\) 的位置一定是 \(j\) 点对于此时的最大 \(a_k\),即此时 \(\max a_k=q_{j+1}\).
那么对于单调队列每相邻两个元素维护 \(dp_j+a_{j+1}\),每次取最小值即可。
同时维护的过程中还要随着单调队列移动,动态删除点,查询最小值,可以用 STL 中的 multiset。
注意:当单调队列长度 \(\ge2\) 时才对队列进行相关操作。

代码

#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int INF = 0x3f3f3f3f,N = 3e5+10;
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;
}

int n,m;
ll a[N],sum[N],dp[N];
inline ll Sum(int l,int r){ return l>r?-1e18:sum[r]-sum[l-1];}
multiset<ll>s;
ll q[N];
int main()
{
	memset(dp,0x3f,sizeof(dp));
	n=read(),m=read();
	for(int i=1;i<=n;i++) a[i]=read(),sum[i]=sum[i-1]+a[i];
	int l=1,r=0;
	int j=0;
	dp[0]=0;//初始化 
	for(int i=0;i<=n;i++) //注意把i=0的情况放入队列 
	{
		while(Sum(j+1,i)>m) j++;
		while(l<=r&&q[l]<j) 
		{
			if(r-l+1>=2) s.erase(s.find(dp[q[l]]+a[q[l+1]]));
			l++; 
		}
		while(l<=r&&a[q[r]]<=a[i]) 
		{	
			if(r-l+1>=2) s.erase(s.find(dp[q[r-1]]+a[q[r]]));
			r--;
		}
		if(l<=r) s.insert(dp[q[r]]+a[i]);
		q[++r]=i;
		dp[i]=dp[j]+a[q[l]];
		if(r-l+1>=2) dp[i]=min(dp[i],*s.begin());
	}
	printf("%lld",dp[n]);
	return 0;
}

参考博客

Last-Order

【ybtoj】【单调队列】出题方案

上一篇:pipenv学习记录(三)


下一篇:leetcode 寻找峰值 中等