CF1270H Number of Components

一、题目

点此看题

二、解法

真的好题啊,我这个垃圾感受到了思维的锤炼。

一开始我想的是做单调栈,我们维护一个递减的单调栈,每次插入一个数就把权值小于它的元素合并到一起,定义合并元素的权值为原来所有元素的权值最小值,连通块个数就是最后栈中元素个数。

显然单调栈是动态维护不了的,但是我们可以从中看出一个奇妙的性质:连通块对应原序列的一个区间。

所以可以做一个问题转化,我们求出有多少个分界点 \(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");
	}
}
上一篇:拒绝冗余,不再重复编写头部与底部


下一篇:利用nodejs的require.context来实现不用写impor导入组件