一、题目
二、解法
真的好题啊,我这个垃圾感受到了思维的锤炼。
一开始我想的是做单调栈,我们维护一个递减的单调栈,每次插入一个数就把权值小于它的元素合并到一起,定义合并元素的权值为原来所有元素的权值最小值,连通块个数就是最后栈中元素个数。
显然单调栈是动态维护不了的,但是我们可以从中看出一个奇妙的性质:连通块对应原序列的一个区间。
所以可以做一个问题转化,我们求出有多少个分界点 \(p\) 满足 \(\forall x\in[1,p],y\in[p+1,n],a_x>a_y\)
这个问题可以再转成对于原序列中的权值 \(v\),大于 \(v\) 的值设置为 \(0\),小于等于 \(v\) 的值设置成 \(1\),求生成 \(01\) 序列形如这样\(111...11000...00\) 的权值个数。
对于每个 \(v\) 我们维护生成序列 \(10\) 相邻数对的个数,如果数对个数为 \(1\) 就是合法的答案。可以用线段树维护,对于原序列上的两个位置 \(i,i+1\),\(v\in[\min(a_i,a_{i+1}),\max(a_i,a_{i+1}))\) 的数对个数会增加 \(1\),区间修改即可。
为了方便我们设 \(a_0=inf,a_{n+1}=-inf\),因为 \(10\) 数对个数至少为 \(1\),所以我们维护最小值和最小值的数量即可,时间复杂度 \(O(n\log n)\)
三、总结
对于排除错误的思路:单调栈是不可能动态维护的(除非特殊情况转成笛卡尔树),想清楚我们要的是什么(本题只需要求元素个数),那么我们把问题转化只关心我们想要的。
序列图论题的结论思考方向:图论某个量和原序列区间的联系。
序列大小关系的处理可以转 \(01\) 序列(比如著名的 \(01\) 原则),大于某个权值设为 \(1\),否则设为 \(0\),然后研究 \(01\) 序列的性质。
维护某一个特定值的数量,思考他是否一定是最值,是的话转成维护最值和最值的数量。
#include <cstdio>
#include <iostream>
using namespace std;
const int inf = 0x3f3f3f3f;
const int M = 1000005;
int read()
{
int x=0,f=1;char c;
while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
return x*f;
}
int n,m,q,a[M],ad[4*M];
struct node
{
int v,c;
node(int V=inf,int C=0) : v(V) , c(C) {}
node operator + (const node &b) const
{
node r;
r.v=min(v,b.v);
if(r.v==v) r.c+=c;
if(r.v==b.v) r.c+=b.c;
return r;
}
}tr[4*M];
void add(int i,int x)
{
ad[i]+=x;tr[i].v+=x;
}
void down(int i)
{
if(!ad[i]) return ;
add(i<<1,ad[i]);
add(i<<1|1,ad[i]);
ad[i]=0;
}
void up(int i)
{
tr[i]=tr[i<<1]+tr[i<<1|1];
}
void ins(int i,int l,int r,int id,int f)
{
if(l==r)
{
if(f==0) tr[i]=node(inf,0);
else tr[i]=node(ad[i],1);
return ;
}
int mid=(l+r)>>1;down(i);
if(mid>=id) ins(i<<1,l,mid,id,f);
else ins(i<<1|1,mid+1,r,id,f);
up(i);
}
void upd(int i,int l,int r,int L,int R,int x)
{
if(L>r || l>R) return ;
if(L<=l && r<=R)
{
add(i,x);
return ;
}
int mid=(l+r)>>1;down(i);
upd(i<<1,l,mid,L,R,x);
upd(i<<1|1,mid+1,r,L,R,x);
up(i);
}
void work(int i,int f)
{
int l=min(a[i],a[i+1]),r=max(a[i],a[i+1]);
upd(1,0,m,l,r-1,f);
}
int main()
{
n=read();m=1e6;q=read();
for(int i=1;i<=n;i++)
a[i]=read(),ins(1,0,m,a[i],1);
a[0]=m+1;a[n+1]=0;
for(int i=0;i<=n;i++) work(i,1);
while(q--)
{
int x=read();
//delete
work(x-1,-1);work(x,-1);ins(1,0,m,a[x],0);
//add
a[x]=read();
work(x-1,1);work(x,1);ins(1,0,m,a[x],1);
if(tr[1].v==1) printf("%d\n",tr[1].c);
else puts("0");
}
}