P3572 [POI 2014] PTA-Little Bird——简单的单调队列

· · 个人记录

初见思路

注意到 $q$ 很小,直接每一个都跑一遍 $dp$ 是可行的 设 $dp_i$ 为到第 $i$ 棵树时的最小劳累值,易得 $$dp_i=min_{i-k\le j<i}(dp_j+[d_j<=d_i])$$ 时间复杂度 $O(qnk)$ ,肯定过不了 ### 走歪了的线段树 可以看到**有一个大小为 $k$ 的滑动窗口,可以尝试单调队列优化** 但是转移的过程中要用到对应状态的 $d$ ,所以考虑分类讨论 如果按照大小分类讨论,并且这个作为标准的量会不断变化的话,类似**权值线段树**的方法是很合适的 注意到虽然 $d$ 的值域很大,但是数量只有 $n$ 级别 考虑离散化,再针对每一个 $d$ 开一个单调队列,在离散化后的值域这一维上建一棵线段树,维护对应区间的最小值 这样就可以进行愉快的 $dp$ 了 时间复杂度 $O(qn\log n)$ ,非常危险(事实上就是没过) ### 正统的单调队列 认真想一想, $d$ 的大小关系对答案的影响**并没有那么大**(此处专门指**单调性**) 如果从 $dp$ 值各不同的位置中选择一个位置转移,那么 $dp$ 值最小的**一定是最优解之一** 因为如果从非最小的 $dp$ 值切换到最小的 $dp$ 值,答案**一定不会变劣**,这是因为就算 $dp$ 值只减小了 $1$ ,也能弥补高度的劣势 所以,当需要转移的时候,最优的选择**一定在 $dp$ 值最小的那一批里** 此时,这些状态中当然是高度越高越好,因为越高越有可能去掉 $1$ 的代价变为最优 所以,可以考虑将这些带有高度和 $dp$ 值的节点丢进单调队列里,其中越靠前的节点: - $dp$ 值更小 - 高度更高 优先保证第一点 最后,我们试一试变换不同的高度,会发现队列内**永远是单调的**,因为对于一个节点,就算前面的同 $dp$ 值的部分都减少了 $1$ 的代价,也能和前面更优的 $dp$ 值接起来形成单调递减的结构 时间复杂度 $O(nq)$ ,很稳 ### AC Code: ```cpp #include <bits/stdc++.h> using namespace std; const int MAXN=1e6+5; const int inf=0x3f3f3f3f; struct node{ int val,h; bool operator<(node y){ if(val==y.val) return h>y.h; return val<y.val; }bool operator==(node y){ return (val==y.val&&h==y.h); } }; struct ddqueue{ list<node> q; void push(node x){ while(!q.empty() && x<q.back()) q.pop_back(); q.push_back(x); }void pop(node x){ if(q.empty()) return ; if(q.front()==x) q.pop_front(); }int query(int h){ if(q.empty()) return inf; return q.front().val+(q.front().h<=h); }// }q; int dp[MAXN],d[MAXN],n,m; void getdp(int k){ q.q.clear(); dp[1]=0;q.push(node{0,d[1]}); for(int i=2;i<=k;++i){ dp[i]=q.query(d[i]); q.push(node{dp[i],d[i]}); }for(int i=k+1;i<=n;++i){ dp[i]=q.query(d[i]); q.push(node{dp[i],d[i]}); q.pop(node{dp[i-k],d[i-k]}); } printf("%d\n",dp[n]); }// void work(){ scanf("%d",&n); for(int i=1;i<=n;++i) scanf("%d",&d[i]); scanf("%d",&m); for(int i=1,k;i<=m;++i){ scanf("%d",&k); getdp(k); } }// int main(){ work(); return 0; } ```