DP提高专项3

本场比赛难度吧不大,建议开题顺序为 T2T1T3

T2

题目描述

n 个高楼排成一行,每个楼有一个高度 hi。称可以从楼 i 跳到 楼 j,当 i, j ( i<j )满足以下三个条件之一:

  • i+1=j

  • max(hi+1,hi+2,,hj1)<min(hi,hj)

  • min(hi+1,hi+2,,hj1)>max(hi,hj)

现在你在楼 1,请求出跳到楼 n 最少要跳几次。

2n3105, 1hi109

思路点拨

没有什么思维的题。考虑动态规划,暴力转移显然是 O(n2) 的。

但是注意到条件 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 的一段,我们去除前 kc 小的元素。

我们希望剩下的数尽量小。

n105

思路点拨

考虑转话题意,希望删除的数尽量大。那么我们知道,删除的数的数量相同的情况小,子段越长,那么最小值也就跟小。

所以说我们要选择长度为 1 的段,要么选择长度为 c 的段。

我们设 dpi 表示考虑到下标 i ,剩下数的最大值,转移显然:

dpi=max{dpi1,dpic+mxic+1,i}

发现 mx 是一个滑动窗口,所以用单调队列优化。

时间复杂度线性。

T3

题目描述

给你一个正整数序列 a1,a2,...an,计算有多少个正整数序列 b1,b2,...bn,满足:

  1. 1biaii[1,n]
  2. bibi+1i[1,n)

答案对 998244353 取模。

n105

思路点拨

n,a 都比较小的时候,有一个暴力 dp 的做法。

dpi,j 表示考虑到第 i 个元素,这个值为 j 的方案,转移显然:

dpi,j=0<kai1dpi1,k[jk]

我们考虑使用数据结构优化这个过程。

我们的转移可以表述为:

dpi,j=dpi,j+0<kai1dpi1,k

我们考虑将这个 dp 数组放进一个动态开点权值线段树里面。那么每一次我们从 dpi1dpi 转移的时候,我们可以:

  • 将全部的 dp 值取反

  • 加上原来权值线段树全部 dp 值得和

  • dpi,j(j>ai) 的部分清空

在这个东西可以使用线段树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 个数是 ai,选取一个连续子序列 {ax,ax+1,,ax+k1} 使得 i=1kiax+i1 最大。

其中1n2×105,|ai|107

思路点拨

考虑一个区间 [l,r] 的贡献 :

i=lrai×(il+1)=i=lriai+(1l)i=lrai

我们令 Ai=i=1iai,Bi=i=1iiai 。那么答案就是:

(BrBl1)+(1l)(ArAl1)

现在,我们考虑暴力的求解,也就是 O(n2) 枚举左端点,那么对于一个右端点 r:

maxi=1r{BrBl1+(1l)(ArAl1)}

对于这个 (BrBl1)+(1l)(ArAl1) 而言,Br 是常数,我们不考虑,所以答案:

Br+maxi=1r{Bl1+(1l)(ArAl1)}

我们考虑一个一次函数 kx+b,对应到式子就是

k=(1l),b=Bl1(1l)Al1,x=Ar

所以可以使用李超线段树维护。

时间复杂度 O(nlogn)。代码比较简单,不放了。

posted @   Diavolo-Kuang  阅读(20)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示