给定一个长 的元素两两不同的序列 ,,若 ,则让 向 连一条无向边。
次修改数组某个位置的值,每次修改后输出图中连通块个数。
,,保证任意时刻数组中元素两两不同。
首先有个结论:
若 和 满足 ,则 , 与 、 连通。
证明是显然的。
根据这个结论,每个连通块一定是一个连续段。
那么注意到 , 和 不在同一个连通块中当且仅当 。而由于每个连通块都是连续段,所以连通块个数就是这样的 的个数 。
定义 的前驱 为满足 且 最大的 ,令 ,则一定有:
- ;
- 中 的个数就是连通块个数,因为每个 的 显然都满足 ;
那么用 set 维护前驱后继、线段树维护 中最小值以及最小值个数即可。
代码如下:
#include <iostream>
#include <cstdio>
#include <set>
using namespace std;
const int S=500005;
int n,q,a[S];
int tag[S<<2],mn[S<<2],mnc[S<<2];
set<pair<int,int>> st;
inline void upda(int u)
{
int ls=u<<1,rs=u<<1|1;
mnc[u]=0;
mn[u]=min(mn[ls],mn[rs]);
if(mn[ls]==mn[u]) mnc[u]+=mnc[ls];
if(mn[rs]==mn[u]) mnc[u]+=mnc[rs];
}
inline void addtag(int u,int val)
{
tag[u]+=val;
mn[u]+=val;
}
inline void dwntag(int u)
{
if(tag[u]==0) return;
addtag(u<<1,tag[u]),addtag(u<<1|1,tag[u]);
tag[u]=0;
}
void build(int u,int l,int r)
{
if(l==r) return tag[u]=0,mn[u]=0,mnc[u]=1,void();
int mid=l+r>>1;
build(u<<1,l,mid),build(u<<1|1,mid+1,r);
upda(u);
}
void upd(int u,int l,int r,int L,int R,int val)
{
if(l>R||r<L) return;
if(l>=L&&r<=R) return addtag(u,val);
dwntag(u);
int mid=l+r>>1;
if(L<=mid) upd(u<<1,l,mid,L,R,val);
if(R>=mid+1) upd(u<<1|1,mid+1,r,L,R,val);
upda(u);
}
inline void updd(int l,int r,int x)
{
if(l==0||r==n+1) return;
r=min(r,n);
if(l+1>r) return;
upd(1,1,n,l+1,r,x);
}
inline void ins(int p,int x)
{
st.insert(make_pair(x,p));
auto lb=st.lower_bound(make_pair(x,p));
auto rb=st.lower_bound(make_pair(x,p));
lb--,rb++;
updd(lb->second,rb->second,-1);
updd(lb->second,p,1);
updd(p,rb->second,1);
}
inline void del(int p,int x)
{
auto lb=st.lower_bound(make_pair(x,p));
auto rb=st.lower_bound(make_pair(x,p));
lb--,rb++;
updd(lb->second,p,-1);
updd(p,rb->second,-1);
updd(lb->second,rb->second,1);
st.erase(make_pair(x,p));
}
int main()
{
scanf("%d%d",&n,&q);
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
build(1,1,n);
st.insert(make_pair(-1e9,0)),st.insert(make_pair(1e9,n+1));
for(int i=1;i<=n;i++) ins(i,a[i]);
while(q-->0)
{
int p,x;
scanf("%d%d",&p,&x);
del(p,a[p]);
a[p]=x;
ins(p,a[p]);
printf("%d\n",mnc[1]);
}
return 0;
}