【CF1270H】Number of Components
题目
题目链接:https://codeforces.com/problemset/problem/1270/H
给一个长度为 \(n\) 的数组 \(a\),\(a\) 中的元素两两不同。
对于每个数对 \((i,j)(i<j)\),若 \(a_i<a_j\),则让 \(i\) 向 \(j\) 连一条边。求图中连通块个数。
支持 \(q\) 次修改数组某个位置的值,每次修改后输出图中连通块个数。
\(n,q\le 5\times 10^5,1\le a_i\le 10^6\),保证任意时刻数组中元素两两不同。
思路
有一个性质:一个连通块一定是序列里的一个区间。
设 \(i<j\),\(k\in(i,j)\),且 \(i,j\) 在一个连通块内:
- 如果 \(a_i<a_j\),那么显然至少满足 \(a_i<a_k\) 或 \(a_j>a_k\) 中的一个。
- 如果 \(a_i>a_j\),必然存在 \(x<i\) 且 \(a_x<a_j<a_i\) 或者 \(y>j\) 且 \(a_y>a_i>a_j\),拿前者举例,\([x,i]\) 与 \([x,j]\) 都必然在同一个连通块内,\([i,j]\) 也就在同一个连通块内。
那么问题就转化为有多少个位置 \(p\) 满足 \(\min(a_1\cdots a_{p-1})>\max(a_p\cdots a_n)\)。
考虑枚举 \(v=\max(a_p\cdots a_n)\),可以把 \(>v\) 的设为 \(1\),\(\leq v\) 的设为 \(0\),那么这个 \(v\) 能分割两个连通块当且仅当这个 \(01\) 序列单调不增。
设 \(a_0=+\infty,a_{n+1}=0\),那么对于任意的 \(v\),其 \(01\) 序列至少包含一个 \(0\) 和一个 \(1\),那么问题又可以转化为求有多少个 \(v\) 满足其对应的 \(01\) 序列只存在一组 \(0,1\) 相邻。
对值域建立一棵线段树,对于相邻的元素 \(a_i,a_{i+1}\),他们能对 \([\min(a_i,a_{i+1}),\max(a_i,a_{i+1}))\) 里面的 \(v\) 贡献一组相邻的 \(0,1\)。那么就将这个区间加 \(1\)。每次询问只需要查询有多少个在序列里的数字线段树上只有 \(1\) 点贡献即可。
时间复杂度 \(O((Q+n)\log V)\)。
代码
#include <bits/stdc++.h>
using namespace std;
const int N=1000010,lim=1e6+1;
int n,Q,a[N];
struct SegTree
{
int cnt[N*4],minv[N*4],lazy[N*4];
void pushdown(int x)
{
if (!lazy[x]) return;
minv[x*2]+=lazy[x]; minv[x*2+1]+=lazy[x];
lazy[x*2]+=lazy[x]; lazy[x*2+1]+=lazy[x];
lazy[x]=0;
}
void pushup(int x)
{
minv[x]=min(minv[x*2],minv[x*2+1]); cnt[x]=0;
if (minv[x]==minv[x*2]) cnt[x]+=cnt[x*2];
if (minv[x]==minv[x*2+1]) cnt[x]+=cnt[x*2+1];
}
void update1(int x,int l,int r,int ql,int qr,int v)
{
if (ql<=l && qr>=r) { minv[x]+=v; lazy[x]+=v; return; }
pushdown(x);
int mid=(l+r)>>1;
if (ql<=mid) update1(x*2,l,mid,ql,qr,v);
if (qr>mid) update1(x*2+1,mid+1,r,ql,qr,v);
pushup(x);
}
void update2(int x,int l,int r,int k,int v)
{
if (l==r) { cnt[x]+=v; return; }
pushdown(x);
int mid=(l+r)>>1;
if (k<=mid) update2(x*2,l,mid,k,v);
if (k>mid) update2(x*2+1,mid+1,r,k,v);
pushup(x);
}
int query(int x,int l,int r,int ql,int qr)
{
if (ql<=l && qr>=r) return (minv[x]==1)?cnt[x]:0;
pushdown(x);
int mid=(l+r)>>1,res=0;
if (ql<=mid) res+=query(x*2,l,mid,ql,qr);
if (qr>mid) res+=query(x*2+1,mid+1,r,ql,qr);
return res;
}
}seg;
int main()
{
scanf("%d%d",&n,&Q);
a[0]=lim;
for (int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
seg.update1(1,0,lim,min(a[i],a[i-1]),max(a[i],a[i-1])-1,1);
seg.update2(1,0,lim,a[i],1);
}
seg.update1(1,0,lim,0,a[n]-1,1);
while (Q--)
{
int x,y;
scanf("%d%d",&x,&y);
seg.update1(1,0,lim,min(a[x],a[x-1]),max(a[x],a[x-1])-1,-1);
seg.update1(1,0,lim,min(a[x],a[x+1]),max(a[x],a[x+1])-1,-1);
seg.update2(1,0,lim,a[x],-1);
a[x]=y;
seg.update1(1,0,lim,min(a[x],a[x-1]),max(a[x],a[x-1])-1,1);
seg.update1(1,0,lim,min(a[x],a[x+1]),max(a[x],a[x+1])-1,1);
seg.update2(1,0,lim,a[x],1);
printf("%d\n",seg.query(1,0,lim,1,lim-1));
}
return 0;
}