AGC063B

发布时间 2023-11-12 13:20:26作者: mRXxy0o0

题意

通过不断在某个位置添加 \((1,2,\dots ,k)\) 所形成的序列称为可生成的。求给定序列有多少区间是可生成的。

分析

我们把一个可生成的序列看成很多依次加一的区间 \((x,x+1,\dots,y)\) 构成的,很明显发现,对于每一个区间,总是满足前面有一段的结尾是 \(x-1\)\(x=1\)

这样的限制条件只在乎前面的,而不用考虑后面的,所以可以得到结论:

如果一个序列是可生成的,那么它的每一个前缀都是可生成的。

有了这个性质,可以考虑枚举左端点,然后找到最右边的点,使这个区间是可生成的。虽然看上去可以二分,但是很快发现,无法在 \(O(1)\) 时间判断一个区间是不是可生成的。这个思路就无法再继续了。

继续观察序列,又发现一个性质:

如果一个可生成的序列包含了另一个可生成的序列,那么被包含的序列一定是连续的。

举个例子,\((1,2,1,2,3,3,1,2,4)\) 就是这样的关系,里面的 \((1,2,3)\)\((1,2)\) 是连续的。

看上去这个性质十分的废话,实际上,这等同于告诉我们:如果剔除了一个可生成的序列内部的其他可生成序列,最后会剩下以 \(1\) 开始,依次加一的序列。

实现上,可以使用链表,倒序遍历,算出每个 \(1\) 最远到达的地方。如果遍历到 \(1\),就利用之前得出的答案往后枚举,并记录当前的答案,每个元素只会枚举两次,复杂度 \(O(n)\)

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
const int N=5e5+10;
int n,a[N],l[N],r[N];
ll ans;
int main(){
	scanf("%d",&n);
	for(int i=1;i<=n;++i){
		scanf("%d",&a[i]);
		r[i]=i+1,l[i]=i-1;
	}
	for(int i=n;i>=1;--i){
		if(a[i]==1){
			int ff=1;
			for(int j=i,k=1;j<=n;j=r[j],++k){
				if(a[j]!=k){
					r[i]=j;
					l[j]=i;
					ans+=j-i;
					ff=0;
					break;
				}
			}
			if(ff){
				ans+=n-i+1;
				r[i]=n+1;
				l[n+1]=i;
			}
			r[l[i]]=r[i];
			l[r[i]]=l[i];
		}
	}
	printf("%lld",ans);
	return 0;
}