2020ICPC·小米 网络选拔赛第一场 E.Phone Network(线段树二分/单调性)

题目

给定一个长为n(n<=2e5)的数组a[],再给一个阈值m(m<=n),

保证1<=ai<=m,且[1,m]的数每个都至少在数组中出现一次

对于每个i属于[1,m],询问区间内 值域的并集包含了[1,i]的所有数的 最短的连续区间是多长

思路来源

官方题解

https://ac.nowcoder.com/acm/contest/view-submission?submissionId=45328431 北大逆十字队代码

题解

官方题解和北大逆十字队20min一血代码一模一样,就tm离谱

首先考虑O(n^{2})的维护这个东西,再考虑后续优化,

每个位置维护当前端点往右的包含[1,i]的最小右端点,

状态是n^{2}的,但是只有n个数会带来更新,

如果能每次更新只有logn个状态,复杂度就降下来了

 

每次新加进来一个数i+1的时候,对于[p_{j-1}+1,p_{j}]之间的数,

r(i+1,l)要么往右挪成p_{j},要么本身的值就大于p_{j},从而不变

注意到维护的这个东西显然单调,即r(i,l)<=r(i,l+1)

这说明,[p_{j-1}+1,p_{j}]之间存在一个分界点x,

[p_{j-1}+1,x]是要往右挪成p_{j}的,这一段需要区间赋值成p_{j}

[x+1,p_{j}]是保持不变的,这个分界点x可以线段树上二分求,

 

为了实现线段树二分,需要维护在非叶节点上维护r(i,l)的最大值,

从而用于二分>=v的最小位置

 

而求答案时,则需用每个[l,r(i,l)]更新i的答案,

这就是r(i,l)-l+1,取个最小值

所以线段树内,再维护一个r(i,l)-l的最小值即可

代码

#include<bits/stdc++.h>
using namespace std;
#define pb push_back
const int N=2e5+10;
int n,m,v;
vector<int>pos[N];
struct segtree{
	int n;
	//cov就是正常的区间赋值标记 
	//r(i,l)表示对于l来说凑齐[1,i]的最小右端点 滚掉第一维 
	//叶子结点的mxr即r(i,l)
	//分支节点是所有叶子结点的max 用于线段树二分 
	//ans用于维护min(r(i,l)-l),此时+1即为答案 
	struct node{int l,r,cov,mxr,ans;}e[N<<2];
	#define l(p) e[p].l
	#define r(p) e[p].r
	#define c(p) e[p].cov
	#define m(p) e[p].mxr
	#define a(p) e[p].ans
	void up(int p){m(p)=max(m(p<<1),m(p<<1|1));a(p)=min(a(p<<1),a(p<<1|1));}
	void bld(int p,int l,int r){
		l(p)=l;r(p)=r;c(p)=0;
		if(l==r){m(p)=l;a(p)=0;return;}//不妨假设最开始都只需要1的长度即可凑齐0 
		int mid=l+r>>1;
		bld(p<<1,l,mid);bld(p<<1|1,mid+1,r);
		up(p);
	}
	void psd(int p){
		if(c(p)){
			m(p<<1)=c(p<<1)=c(p);
			a(p<<1)=m(p<<1)-r(p<<1);
			m(p<<1|1)=c(p<<1|1)=c(p);
			a(p<<1|1)=m(p<<1|1)-r(p<<1|1);
			c(p)=0;
		}
	}
	void init(int _n){n=_n;bld(1,1,n);}
	void chg(int p,int ql,int qr,int v){
		if(ql>qr)return;
		if(ql<=l(p) && r(p)<=qr){
			c(p)=m(p)=v;
			a(p)=v-r(p);
			return;
		}
		psd(p);
		int mid=l(p)+r(p)>>1;
		if(ql<=mid)chg(p<<1,ql,qr,v);
		if(qr>mid)chg(p<<1|1,ql,qr,v);
		up(p);
	}
	int find(int p,int v){//线段树二分>=v的最左位置 
		if(l(p)==r(p))return l(p);
		psd(p); 
		int mid=l(p)+r(p)>>1;
		if(m(p<<1)>=v)return find(p<<1,v);
		return find(p<<1|1,v);
	}
}seg;
int main(){
	scanf("%d%d",&n,&m);
	for(int i=1;i<=m;++i){
		pos[i].pb(0);
	}
	for(int i=1;i<=n;++i){
		scanf("%d",&v);
		pos[v].pb(i);
	}
	seg.init(n);
	for(int i=1;i<=m;++i){
		int sz=pos[i].size();
		if(pos[i][sz-1]+1<=n){
			seg.chg(1,pos[i][sz-1]+1,n,1000000000);//凑不成 即INF 
		}
		for(int j=sz-1;j>=1;--j){
			int ub=seg.find(1,pos[i][j])-1;
			seg.chg(1,pos[i][j-1]+1,ub,pos[i][j]);
		}
		printf("%d%c",seg.e[1].ans+1," \n"[i==m]);
	}
	return 0;
} 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Code92007

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值