Phone Network(小米邀请赛)

Phone Network

题目大意

有一个长度为 n 的数组,里面有每个值为 1–m之间且每个数出现的次数至少为1.问分别求出每个包含从 1–i 的最短区间的长度。

解题思路

这个线段是真的是涨姿势呀!!!
题解
在构建线段树之前我们分别记录每种节点出现的位置。
这样我们构建线段树的时候,从第一种节点开始构建,线段树记录每个节点到 包含 1–i 号节点的最短距离。ma记录的包含这个区间的最远位置,如果我们查询每个节点的位置是浮现它对应的ma已经大于当前的坐标了,就不用更新了。
比如说 从 1 3 2
记录完以后 三个数对应的 ma 都是 3 3 3 (2的pos 是3)
再更新的时候 pos 值时 2 所以用更新了。

代码

#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int mx=200100;
const int inf=100010000;
int n,m;
struct node{
// ma 为距离其最远的右端点	
	int ma;
// 记录答案	
	int ans;
	int lazy;
	int l,r;
}a[mx<<2]; 
vector<int> ve[mx];

void build(int x,int l,int r){
	a[x].ans=inf;
	a[x].ma=0;
	a[x].lazy=0;
	a[x].l=l;
	a[x].r=r;
	if(l==r){
		return ;
	}
	int mid=l+r>>1;
	build(x<<1,l,mid);
	build(x<<1|1,mid+1,r);
}

void up_down(int x){
	int k=a[x].lazy;
	if(k==0) return ;
	a[x<<1].lazy=k;
	a[x<<1|1].lazy=k;
	
	a[x<<1].ma=k;
	a[x<<1|1].ma=k;
	
	a[x<<1].ans=k-a[x<<1].r+1;
	a[x<<1|1].ans=k-a[x<<1|1].r+1;
	a[x].lazy=0;
	return ;
}

int query(int x,int ql,int qr){
	if(a[x].ma>=qr) return -1;
	if(a[x].l>qr||a[x].r<ql)
		return -1;
//	这里为什么不能是  a[x].l>=ql&&a[x].r 呢?
// 因为我们需要知道是最靠近的 x 这个节点的
// 按照这样找的是这个区间内最左短的
// 有可能这个区间中间的一部分也不是可以的
// 所以这里要找到 的是点 
	if(a[x].l==a[x].r){
		return a[x].l;
	}
	up_down(x); 
	int	ans=query(x<<1|1,ql,qr);
	if(ans==-1) ans=query(x<<1,ql,qr);
	return ans;
}

void up(int x,int l,int r,int k){
	if(l>r) return;
	if(a[x].l>r||a[x].r<l) return;
	if(a[x].l>=l&&a[x].r<=r){
		a[x].ans=k-a[x].r+1;
		a[x].ma=k;
		a[x].lazy=k;
		return ;    
	}
	up_down(x);
	up(x<<1,l,r,k);
	up(x<<1|1,l,r,k);
	a[x].ans=min(a[x<<1].ans,a[x<<1|1].ans);
	a[x].ma=min(a[x<<1].ma,a[x<<1|1].ma);
}
int main(){
	ios::sync_with_stdio(0);
	cin>>n>>m;
	for(int i=1,x;i<=n;i++){
		cin>>x;
		ve[x].push_back(i);
	} 
	build(1,1,n);
	for(int i=1,pos,per;i<=m;i++){
		per=0;
		for(int p:ve[i]){
// 查找以p为右端点 最靠近且满足情况的左端点。			
			pos=query(1,per+1,p);
			if(pos!=-1) up(1,per+1,pos,p);
			per=p;
		}
// 这里 x 的城市覆盖的最广才到	ve[i].back
// 所以我们需要把后面的全部覆盖成最大值,防止
// 后面的部分影响前面的。	
		if(ve[i].back()<n){
			up(1,ve[i].back()+1,n,inf);
		}
		cout<<a[1].ans<<" \n"[i==m];
	}
	return 0;
} 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值