zoukankan      html  css  js  c++  java
  • 【YBTOJ】【单调队列优化DP】出题方案

    出题方案

    现在小泽的手上有 (n) 道难题,编号分别为 (1sim n) ,第 (i) 道题的难度系数是 (a_i)

    小泽想用这些题出比赛,他会把题目按照编号划分为若干个非空连续区间,每个区间对应了一场比赛。

    特别的,如果某场比赛的题目难度系数之和超过了给定的常数 (m) ,这场比赛会过于毒瘤,所以他不希望出现这样的情况。

    定义每场比赛的难度系数为这场比赛所有题目难度系数的最大值。

    小泽想让你找出一组合法方案,使得所有比赛的难度系数之和最小。

    (nleq3 imes10^5)(1leq a_ile10^6)(1leq m leq 1.1 imes10^9)

    题解

    静下心来想这道题吧!

    (dp_i) 表示把前 (i) 个数分段后最小代价。

    则朴素 dp : (dp_i=min{dp_j+maxlimits_{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})
      (jin(k_{i},k_{i+1}]) 内的 (dp_j+maxlimits_{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) 满足 (sumlimits _{k=j+1}^i le m) , 且 (j< i)

    概述一下:

    • 对于每个 (i) ,求出第一个满足条件的 (j) 。维护一个 ([j,i)) 的单调队列。
    • 求出单调队列中每相邻一对的答案,用数据结构维护,其最小值计入 (dp_i)

    复杂度(O(nlog 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;
    }
    
  • 相关阅读:
    文件权限---I
    python查看变量在内存中的地址
    Python3 基本数据类型
    print语法
    任务管理器启动项显示“没有可显示的启动项”如何解决?
    nginx 虚拟主机配置
    nginx 高级应用
    nginx 日志文件详解
    nginx 编译安装与配置使用
    python获取header脚本
  • 原文地址:https://www.cnblogs.com/Shinomiya/p/15293941.html
Copyright © 2011-2022 走看看