洛谷 P5044 - [IOI2018] meetings 会议(笛卡尔树+DP+线段树)
一道笛卡尔树的 hot tea。
首先我们考虑一个非常 naive 的区间 DP:\(dp_{l,r}\) 表示区间 \([l,r]\) 的答案,那么我们考虑求出 \([l,r]\) 中最大值的位置所在的位置 \(p\),那么如果我们选取的 meeting 的位置 \(\le p\),那么显然 \([p+1,r]\) 部分的贡献都是 \(a_p\),\([l,p]\) 部分的总共先最小是 \(dp_{l,p}\),最优代价为 \(dp_{l,p}+a_p·(r-p)\),否则 \([l,p]\) 部分的贡献都是 \(a_p\),\([p+1,r]\) 部分的总贡献最小是 \(dp_{p+1,p}\),最优代价为 \(dp_{p+1,p}+a_p·(p-l+1)\),二者取 \(\min\) 即可。
直接做最优大概是 \(n^2\) 的,可以获得 19 分的好成绩(雾)。考虑优化,注意到这东西有点像笛卡尔树,因此我们考虑将笛卡尔树建出来。对于此题而言,由于数据范围允许 \(n\log n\) 通过,我们可以使用 \(n\log n\) 的 ST 表建树方法,不必采用那个严格线性的方式。我们考虑对于每组询问 \([L,R]\),找到 \([L,R]\) 区间中最大值所在的位置 \(p\),然后将整个询问拆成两个询问:\([L,p-1]\) 和 \([p+1,R]\)——为什么要这样干呢?原理与 P3246 [HNOI2016]序列 一致,因为这样我们就造出了两个区间,一个满足区间左边那个数大于等于区间中所有数,另一个则满足区间右边那个数大于等于区间中所有数(方便起见,对于后面的部分,我们可以将整个数组翻转一遍然后对翻转后的数组再做一遍这个操作,这样两部分就等价了,注意在后面一半建树部分有些小细节,所有值相同的最大值中,取下标最大的还是最小的要想清楚,不要想当然)这样一来,对于笛卡尔树上每个点 \(x\) 我们如果假设 \(x\) 子树内的点表示的区间左端点为 \(L_x\),右端点为 \(R_x\),那么对于满足“左边的数大于等于区间中所有数的区间 \([L,R]\)”,我们必然能够在笛卡尔树上找到某个点 \(x\),满足 \(L_x=L\),也就是说我们可以将每个询问挂在这个区间中所有点在笛卡尔树的 LCA,也就是 \([L,R]\) 最大值所在的位置表示的点上,然后离线一遍 DFS,DFS 的过程中扫描到点 \(x\) 时,用某种方式维护 \([L_x,L_x],[L_x,L_x+1],\cdots,[L_x,R_x]\) 的 DP 值,这样即可查出答案。
那么怎么维护这样些 DP 值呢?我们开一棵线段树,下标为 \(r\) 的位置实时维护右端点为 \(r\) 的区间的 DP 值。那么假设我们遍历到点 \(x\),\([L_x,R_x]\) 最大值所在的位置为 \(p\),那么我们先遍历 \([L_x,p-1],[p+1,R_x]\),那么对于 \([L_x,p-1]\) 中的位置,其 DP 值肯定不会发生变化,合并的时候就不用管它,对于 \(dp_{L_x,p}\),由于区间 \([L_x,p]\) 最大值就是 \(a_p\),因此我们肯定只能令 meeting 的位置 \(\le p\),此时 \(dp_{L_x,p}=dp_{L_x,p-1}+a_p·(p-L_x+1)\),在线段树上查询 \(p-1\) 位置的值然后做出对应修改即可。
比较棘手的是区间 \([p+1,R_x]\)。对于区间 \([p+1,R_x]\) 中的某个 \(v\),根据上面朴素的 DP 有 \(dp_{L_x,v}=\min(dp_{L_x,p}+(v-p)·a_p,dp_{p+1,v}+(p-L_x+1)·a_p)\),不难发现 \(\min\) 左右两边都是递增的,并且右边的部分增长速度严格慢于左边,因此我们考虑二分找出最右边的位置 \(pos\),满足 \(dp_{L_x,p}+(pos-p)·a_p\le dp_{p+1,pos}+(p-L_x+1)·a_p\),然后令 \([p+1,v]\) 位置中的 DP 值先清零,然后对于所有 \(i\in[p+1,v]\),DP 值假设 \((dp_{L_x,p}-p·a_p)+i·a_p\),\([v+1,p]\) 位置上的值整体加上 \((p-L_x+1)·a_p\),那么怎么找到这个位置 \(pos\) 呢?直接二分是 2log 的,不过注意到如果我们令 \(A=dp_{L_x,p}-p·a_p,B=a_p,C=(p-L_x+1)·a_p)\),那么我们等价于找到最右边的位置满足 \(A+Bi\le C+f(i)\),其中 \(f(i)\) 表示线段树上下标为 \(i\) 的位置上的值,这个一脸线段树二分的样子,线段树上二分一下即可做到 1log。
时间复杂度 \((n+q)\log n\)。
const int MAXN=7.5e5;
const int LOG_N=20;
int n,qu,a[MAXN+5];
pii qp[MAXN+5];
struct solver{
	pii st[MAXN+5][LOG_N+2];
	pii query(int l,int r){
		int k=31-__builtin_clz(r-l+1);
		return max(st[l][k],st[r-(1<<k)+1][k]);
	}
	int ncnt=0,rt=0,ch[MAXN+5][2],md[MAXN+5];
	void build_cat(int &k,int l,int r){
		if(l>r) return;if(!k) k=++ncnt;md[k]=abs(query(l,r).se);
//		printf("%d %d %d\n",l,r,md[k]);
		build_cat(ch[k][0],l,md[k]-1);build_cat(ch[k][1],md[k]+1,r);
	}
	void init(){
		for(int i=1;i<=LOG_N;i++) for(int j=1;j+(1<<i)-1<=n;j++)
			st[j][i]=max(st[j][i-1],st[j+(1<<i-1)][i-1]);
		build_cat(rt,1,n);
	}
	vector<pii> qv[MAXN+5];
	void add_query(int l,int r,int id){
		if(l>r) return;int ps=abs(query(l,r).se);
//		printf("+ %d %d %d %d\n",l,r,id,ps);
		qv[ps].pb(mp(r,id));
	}
	struct node{int l,r;ll val,lz0,lz1,t0;} s[MAXN*4+5];
	void pushup(int k){s[k].val=s[k<<1|1].val;}
	void build(int k,int l,int r){
		s[k].l=l;s[k].r=r;if(l==r) return;int mid=l+r>>1;
		build(k<<1,l,mid);build(k<<1|1,mid+1,r);
	}
	void make0(int k){s[k].t0=1;s[k].val=s[k].lz0=s[k].lz1=0;}
	void tag(int k,ll v0,ll v1){
		s[k].val+=v0+1ll*s[k].r*v1;
		s[k].lz0+=v0;s[k].lz1+=v1;
	}
	void pushdown(int k){
		if(s[k].t0){make0(k<<1);make0(k<<1|1);s[k].t0=0;}
		if(s[k].lz0||s[k].lz1){
			tag(k<<1,s[k].lz0,s[k].lz1);
			tag(k<<1|1,s[k].lz0,s[k].lz1);
			s[k].lz0=s[k].lz1=0;
		}
	}
	void addrng(int k,int l,int r,ll v0,ll v1){
		if(l>r) return;
		if(l<=s[k].l&&s[k].r<=r) return tag(k,v0,v1),void();
		pushdown(k);int mid=s[k].l+s[k].r>>1;
		if(r<=mid) addrng(k<<1,l,r,v0,v1);
		else if(l>mid) addrng(k<<1|1,l,r,v0,v1);
		else addrng(k<<1,l,mid,v0,v1),addrng(k<<1|1,mid+1,r,v0,v1);
		pushup(k);
	}
	void modify(int k,int p,ll x){
		if(s[k].l==s[k].r) return s[k].val=x,void();
		pushdown(k);int mid=s[k].l+s[k].r>>1;
		(p<=mid)?modify(k<<1,p,x):modify(k<<1|1,p,x);
		pushup(k);
	}
	int walk(int k,int l,int r,ll lft,int dlt,ll rit){//the rightmost index i satisfying lft+dlt*i<=rit+f(i)
		if(s[k].l==s[k].r) return (lft+1ll*dlt*s[k].l<=rit+s[k].val)?s[k].l:(s[k].l-1);
		pushdown(k);int mid=s[k].l+s[k].r>>1;
		if(r<=mid) return walk(k<<1,l,r,lft,dlt,rit);
		else if(l>mid) return walk(k<<1|1,l,r,lft,dlt,rit);
		else{
			if(lft+1ll*dlt*mid<=rit+s[k<<1].val) return walk(k<<1|1,mid+1,r,lft,dlt,rit);
			else return walk(k<<1,l,mid,lft,dlt,rit);
		}
	}
	void makezero(int k,int l,int r){
		if(l>r) return;
		if(l<=s[k].l&&s[k].r<=r) return make0(k),void();
		pushdown(k);int mid=s[k].l+s[k].r>>1;
		if(r<=mid) makezero(k<<1,l,r);
		else if(l>mid) makezero(k<<1|1,l,r);
		else makezero(k<<1,l,mid),makezero(k<<1|1,mid+1,r);
		pushup(k);
	}
	ll query_pt(int k,int p){
		if(s[k].l==s[k].r) return s[k].val;
		pushdown(k);int mid=s[k].l+s[k].r>>1;
		return (p<=mid)?query_pt(k<<1,p):query_pt(k<<1|1,p);
	}
	ll res[MAXN+5];
	void solve(int k,int l,int r){
//		printf("solving %d %d %d\n",k,l,r);
		if(!k) return;
		if(!ch[k][0]&&!ch[k][1]){
			modify(1,l,st[l][0].fi);
			for(pii p:qv[l]) res[p.se]=st[l][0].fi;
			return;
		} int mid=md[k];
		solve(ch[k][0],l,mid-1);solve(ch[k][1],mid+1,r);
		ll val=(!ch[k][0])?st[mid][0].fi:(query_pt(1,mid-1)+st[mid][0].fi);
		modify(1,mid,val);
//		printf("%d %d %d %lld\n",l,r,mid,val);
		if(mid<r){
			int pos=walk(1,mid+1,r,val-1ll*st[mid][0].fi*mid,st[mid][0].fi,1ll*st[mid][0].fi*(mid-l+1));
			makezero(1,mid+1,pos);addrng(1,mid+1,pos,val-1ll*st[mid][0].fi*mid,st[mid][0].fi);
			addrng(1,pos+1,r,1ll*(mid-l+1)*st[mid][0].fi,0);
		} for(pii p:qv[mid]) res[p.se]=query_pt(1,p.fi);
	}
	void work(){build(1,1,n);solve(rt,1,n);}
} fw,bk;
int main(){
	scanf("%d%d",&n,&qu);
	for(int i=1;i<=n;i++) scanf("%d",&a[i]);
	for(int i=1;i<=n;i++) fw.st[i][0]=mp(a[i],i);
	for(int i=1;i<=n;i++) bk.st[i][0]=mp(a[n-i+1],-i);
	fw.init();bk.init();
	for(int i=1;i<=qu;i++){
		scanf("%d%d",&qp[i].fi,&qp[i].se);++qp[i].fi;++qp[i].se;
		int ps=fw.query(qp[i].fi,qp[i].se).se;
//		printf("ps=%d\n",ps);
		fw.add_query(ps+1,qp[i].se,i);
		bk.add_query(n-ps+2,n-qp[i].fi+1,i);
	} fw.work();bk.work();
//	for(int i=1;i<=qu;i++) printf("%lld %lld\n",fw.res[i],bk.res[i]);
	for(int i=1;i<=qu;i++){
		ll r1=fw.res[i],r2=bk.res[i];
		int ps=fw.query(qp[i].fi,qp[i].se).se;
		printf("%lld\n",min(r1+1ll*a[ps]*(ps-qp[i].fi+1),r2+1ll*a[ps]*(qp[i].se-ps+1)));
	}
	return 0;
}
洛谷 P5044 - [IOI2018] meetings 会议(笛卡尔树+DP+线段树)的更多相关文章
- 洛谷 P5897 - [IOI2013]wombats(决策单调性优化 dp+线段树分块)
		题面传送门 首先注意到这次行数与列数不同阶,列数只有 \(200\),而行数高达 \(5000\),因此可以考虑以行为下标建线段树,线段树上每个区间 \([l,r]\) 开一个 \(200\times ... 
- 洛谷P4338 [ZJOI2018]历史(LCT,树形DP,树链剖分)
		洛谷题目传送门 ZJOI的考场上最弱外省选手T2 10分成功滚粗...... 首先要想到30分的结论 说实话Day1前几天刚刚刚掉了SDOI2017的树点涂色,考场上也想到了这一点 想到了又有什么用? ... 
- 洛谷P4770 [NOI2018]你的名字 [后缀自动机,线段树合并]
		传送门 思路 按照套路,直接上后缀自动机. 部分分:\(l=1,r=|S|\) 首先把\(S\)和\(T\)的后缀自动机都建出来. 考虑枚举\(T\)中的右端点\(r\),查询以\(r\)结尾的串最长 ... 
- 洛谷P4254 [JSOI2008]Blue Mary开公司(李超线段树)
		题面 传送门 题解 李超线段树板子 具体可以看这里 //minamoto #include<bits/stdc++.h> #define R register #define fp(i,a ... 
- 洛谷P4493 [HAOI2018]字串覆盖(后缀自动机+线段树+倍增)
		题面 传送门 题解 字符串就硬是要和数据结构结合在一起么--\(loj\)上\(rk1\)好像码了\(10k\)的样子-- 我们设\(L=r-l+1\) 首先可以发现对于\(T\)串一定是从左到右,能 ... 
- 【洛谷 P2464】[SDOI2008]郁闷的小J(线段树)
		题目链接 这题我很久之前用分块写过,没写出来.. 今天又看到了,于是下决心把这题做出来. 这次我用线段树写的,直接对每本书的编号Hash一下然后离散化然后各建一棵线段树,维护当前编号在某个位置有没有书 ... 
- 洛谷P4770 [NOI2018]你的名字(后缀自动机+线段树)
		传送门 我有种自己根本没学过SAM的感觉……最后还是抄了老半天的题解…… 首先,对$S$和每一次的$T$都建一个SAM 先考虑一下$l=1,r=\left| S \right|$的情况 设$lim_i ... 
- 洛谷P4364 [九省联考2018]IIIDX   【线段树】
		题目 [题目背景] Osu听过没?那是Konano最喜欢的一款音乐游戏,而他的梦想就是有一天自己也能做个独特酷炫的音乐游戏.现在 ,他在世界知名游戏公司KONMAI内工作,离他的梦想也越来越近了.这款 ... 
- 洛谷P4364 [九省联考2018]IIIDX(线段树)
		传送门 题解看得……很……迷? 因为取完一个数后,它的子树中只能取权值小于等于它的数.我们先把权值从大到小排序,然后记$a_i$为他左边(包括自己)所有取完他还能取的数的个数.那么当取完一个点$x$的 ... 
随机推荐
- Java---String和StringBuffer类
			Java---String和StringBuffer类 Java String 类 字符串在Java中属于对象,Java提供String类来创建和操作字符串. 创建字符串 创建字符串常用的方法如下: ... 
- vue3双向数据绑定原理_demo
			<!DOCTYPE html> <head> <meta charset="UTF-8" /> <meta name="view ... 
- SPI在JDBC中的运用
			前言 之前学习了JDK SPI的机制,本文专门讨论2个内容: 1.为什么在使用SPI后,不需要Class.forName()了? 2.SPI在JDBC中的运用. JDBC模板代码 private st ... 
- ruby基本图片上传
			图片上传问题 在我们的项目里,需要实现海报的图片上传,便于更好地向外界展示一个社团活动的基本内容,但是在处理中间件相关问题时遇到了一点小小的挫折.不过这并不要紧,OSS对象存储服务固然好,但是本着交完 ... 
- java中的软,弱,虚引用介绍与特性分析
			java的弱,虚,软引用介绍 1.弱,虚,软引用的介绍 对于绝大部分的对象而言,在程序中是存在着一个引用变量引用该对象,这是常见的引用方式,也就是常说的 强引用,对于强引用引用的对象,系统JVM是不会 ... 
- Noip模拟77 2021.10.15
			T1 最大或 $T1$因为没有开$1ll$右移给炸掉了,调了一年不知道为啥,最后实在不懂了 换成$pow$就过掉了,但是考场上这题耽误了太多时间,后面的题也就没办法好好打了.... 以后一定要注意右移 ... 
- Noip模拟30 2021.8.4
			T1 毛一琛 考场上打的稳定的$O((2^n)^2)$的暴力.其实再回忆一下上次那道用二进制枚举的题$y$ 就可以知道一样的道理,使用$\textit{Meet In the Middle}$, 按照 ... 
- PWM通过RC低通滤波器模拟DAC
			当我们电路需要DAC而单片机并没有DAC外设时,则可采用PWM通过RC低通滤波器来模拟实现DAC功能. RC低通滤波器 当采用低通滤波器模拟DAC时,PWM频率应远大于RC低通滤波电路的截止频率fc= ... 
- Java并发:AbstractQueuedSynchronizer(AQS)
			队列同步器 AbstractQueuedSynchronizer 是一个公共抽象类.提供一个同步器框架,用于实现依赖于先进先出(FIFO)等待队列的阻塞锁和相关同步器(信号量,事件等).使用一个 in ... 
- JAVA笔记14__多线程共享数据(同步)/ 线程死锁 / 生产者与消费者应用案例 / 线程池
			/** * 多线程共享数据 * 线程同步:多个线程在同一个时间段只能有一个线程执行其指定代码,其他线程要等待此线程完成之后才可以继续执行. * 多线程共享数据的安全问题,使用同步解决. * 线程同步两 ... 
