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;
}