DP提高专项3
本场比赛难度吧不大,建议开题顺序为 \(T2-T1-T3\) 。
\(T2\)
题目描述
有 \(n\) 个高楼排成一行,每个楼有一个高度 \(h_i\)。称可以从楼 \(i\) 跳到 楼 \(j\),当 \(i\), \(j\) ( \(i < j\) )满足以下三个条件之一:
-
\(i+1=j\)
-
\(\max(h_{i+1},h_{i+2},\cdots,h_{j-1})<\min(h_i,h_j)\)
-
\(\min(h_{i+1},h_{i+2},\cdots,h_{j-1})>\max(h_i,h_j)\)
现在你在楼 \(1\),请求出跳到楼 \(n\) 最少要跳几次。
\(2 \leq n \leq 3\cdot 10^5\), \(1\leq h_i \leq 10^9\)。
思路点拨
没有什么思维的题。考虑动态规划,暴力转移显然是 $O(n^2) $ 的。
但是注意到条件 \(2,3\) 本质上是在描述序列的峰谷,我们知道这样的二元组 \((i,j)\) 的数量线性,所以使用单调栈求出。
时间复杂度线性。
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int MAXN=3e5+10;
int n,a[MAXN];
int f[MAXN];
int s1[MAXN],top1,s2[MAXN],top2;
signed main(){
cin>>n;
for(int i=1;i<=n;i++)
cin>>a[i];
s1[++top1]=s2[++top2]=1;
for(int i=2;i<=n;i++){
f[i]=f[i-1]+1;
while(top1&&a[s1[top1]]<=a[i]){
int tmp=a[s1[top1--]];
if(a[i]!=tmp&&top1)
f[i]=min(f[i],f[s1[top1]]+1);
}
while(top2&&a[s2[top2]]>=a[i]){
int tmp=a[s2[top2--]];
if(a[i]!=tmp&&top2)
f[i]=min(f[i],f[s2[top2]]+1);
}//找出全部峰谷
s1[++top1]=s2[++top2]=i;
}
cout<<f[n];
return 0;
}
\(T1\)
题目描述
现在有一个长度为 \(n\) 的序列 \(a\) 和一个常数 \(c\) 。
我们希望将序列分成若干段,那么对于长度为 \(k\) 的一段,我们去除前 \(\lfloor \dfrac{k}{c}\rfloor\) 小的元素。
我们希望剩下的数尽量小。
\(n \leqslant 10^5\) 。
思路点拨
考虑转话题意,希望删除的数尽量大。那么我们知道,删除的数的数量相同的情况小,子段越长,那么最小值也就跟小。
所以说我们要选择长度为 \(1\) 的段,要么选择长度为 \(c\) 的段。
我们设 \(dp_i\) 表示考虑到下标 \(i\) ,剩下数的最大值,转移显然:
发现 \(mx\) 是一个滑动窗口,所以用单调队列优化。
时间复杂度线性。
\(T3\)
题目描述
给你一个正整数序列 \(a_1,a_2,...a_n\),计算有多少个正整数序列 \(b_1,b_2,...b_n\),满足:
- \(1\le b_i \le a_i\),\(i\in [1,n]\)
- \(b_i\neq b_{i+1}\),\(i\in[1,n)\)
答案对 998244353 取模。
\(n \leqslant 10^5\) 。
思路点拨
当 \(n,a\) 都比较小的时候,有一个暴力 \(dp\) 的做法。
设 \(dp_{i,j}\) 表示考虑到第 \(i\) 个元素,这个值为 \(j\) 的方案,转移显然:
我们考虑使用数据结构优化这个过程。
我们的转移可以表述为:
我们考虑将这个 \(dp\) 数组放进一个动态开点权值线段树里面。那么每一次我们从 \(dp_{i-1}\) 向 \(dp_i\) 转移的时候,我们可以:
-
将全部的 \(dp\) 值取反
-
加上原来权值线段树全部 \(dp\) 值得和
-
将 \(dp_{i,j}(j>a_i)\) 的部分清空
在这个东西可以使用线段树2模板解决。
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int MAXN=2e5+10,mod=998244353;
const int N=1e9;
int n,a[MAXN];
int rot,tot;
struct node{
int x,l,r;
int tag1,tag2;//乘法标记,加法标记
node(){tag1=1;}
}t[MAXN*60];
void pushup(int i){
t[i].x=(t[t[i].l].x+t[t[i].r].x)%mod;
}
void pushdown(int i,int l,int r){
int mid=(l+r)>>1;
if(!t[i].l) t[i].l=++tot;
if(!t[i].r) t[i].r=++tot;
t[t[i].l].x=t[t[i].l].x*t[i].tag1%mod;
t[t[i].r].x=t[t[i].r].x*t[i].tag1%mod;
t[t[i].l].tag1=t[t[i].l].tag1*t[i].tag1%mod;
t[t[i].r].tag1=t[t[i].r].tag1*t[i].tag1%mod;
t[t[i].l].tag2=t[t[i].l].tag2*t[i].tag1%mod;
t[t[i].r].tag2=t[t[i].r].tag2*t[i].tag1%mod;
t[t[i].l].x=(t[t[i].l].x+(mid-l+1)*t[i].tag2)%mod;
t[t[i].r].x=(t[t[i].r].x+(r-mid)*t[i].tag2)%mod;
t[t[i].l].tag2=(t[t[i].l].tag2+t[i].tag2)%mod;
t[t[i].r].tag2=(t[t[i].r].tag2+t[i].tag2)%mod;
t[i].tag1=1,t[i].tag2=0;
}
void update(int &i,int l,int r,int L,int R,int w,int o){//区间加w(o=1)|区间乘w(o=2)
if(!i) i=++tot;
pushdown(i,l,r);
if(L<=l&&r<=R){
if(o==1){
t[i].x=(t[i].x+(r-l+1)*w)%mod;
t[i].tag2=w;
}
else{
t[i].x=t[i].x*w%mod;
t[i].tag1=w;
}
return ;
}
else if(l>R||r<L) return ;
int mid=(l+r)>>1;
update(t[i].l,l,mid,L,R,w,o);
update(t[i].r,mid+1,r,L,R,w,o);
pushup(i);
}
signed main(){
cin>>n;
for(int i=1;i<=n;i++) cin>>a[i];
update(rot,1,N,1,a[1],1,1);//初始化dp[i<=a[1]]=1
for(int i=2;i<=n;i++){
int w=t[rot].x;//全体取反之后加上w
update(rot,1,N,1,N,mod-1,0);
update(rot,1,N,1,N,w,1);
if(a[i]<N) update(rot,1,N,a[i]+1,N,0,0);
}
cout<<t[rot].x;
return 0;
}
\(T4\) 附加
题目描述
给一个长度为 \(n\) 的序列,第 \(i\) 个数是 \(a_i\),选取一个连续子序列 \(\{a_x,a_{x+1},\dots ,a_{x+k-1}\}\) 使得 \(\sum_{i=1}^k i\cdot a_{x+i-1}\) 最大。
其中\(1\leq n\leq 2\times 10^5,|a_i|\leq 10^7\)
思路点拨
考虑一个区间 \([l,r]\) 的贡献 :
我们令 \(A_i=\sum_{i=1}^i a_i\),\(B_i = \sum_{i=1}^i ia_i\) 。那么答案就是:
现在,我们考虑暴力的求解,也就是 \(O(n^2)\) 枚举左端点,那么对于一个右端点 \(r\):
对于这个 \((B_r-B_{l-1})+(1-l)(A_r-A_{l-1})\) 而言,\(B_r\) 是常数,我们不考虑,所以答案:
我们考虑一个一次函数 \(kx+b\),对应到式子就是
\(k=(1-l),b=-B_{l-1}-(1-l)A_{l-1},x=A_r\)
所以可以使用李超线段树维护。
时间复杂度 \(O(n \log n)\)。代码比较简单,不放了。