CF1819C The Fox and the Complete Tree Traversal

发布时间 2023-05-24 22:36:45作者: FJOI

\(\color{purple}\text{The Fox and the Complete Tree Traversal}\)

比较有意思的一题。先考虑一个序列的权值。对长度为 \(len\) 的序列排序,价值为 \(len-1\) ,那么有时候如果后面的元素很大,前面的很小:

3 2 1 300 200 100

我们可以将序列切为 \([1,3]\) ,和 \([4,6]\) 两部分分别排序,相比整个排序,权值减小了一。同时我们发现,序列中的排序区间必然不可能相交,因为相邻都只减少一,相交不如直接整个区间排。

触类旁通,如果我们将序列切成 \(n\) 个区间,满足每个区间中的数都大于所有区间前面的数,那么答案就为 \((len-1)-(n-1)\)

我们回到这题,求的是子串序列的权值和。那么易得每出现一组三元组满足 \((l,m,r)\) 满足 \(l\le m< r\) ,且 \(\min[m+1,r]>\max[l,m]\) ,那么就说明在子串 \([l,r]\) 可以在 \(m\) 这个地方切一刀,总答案减一。(总答案的初始值很好求的对吧)。

我的第一想法是枚举 \(l,m\)\(r\) 的可选区间显然连续,那么用二分和 \(ST\) 表求出 \(r\) 的最后可选点。复杂度 \(O(n^2)\)

但这显然通过不了。更优的做法是枚举 \(a[i]\) 作为 \(\max[l,m]\) 时,\(l,m\) 就被定下来了,因为\(m\) 前面的数都得小于 \(a[i]\)\(m\) 后面的数都大于 \(a[i]\) ,所以显然 \(m\) 就是 \(a[i]\) 后面第一个大于等于 \(a[i]\) 的数(单调栈),而 \(l\) 更不用说, \([l,i]\) 中的数得小于 \(a[i]\) ,求出 \(l\) 的可选区间,然后 \(r\) 同第一想法一样求出可选区间,复杂度就是 \(O(n)\) 了。

点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=5e5+110;
int read(){
	int x=0,f=1;char c=getchar();
	while(c>'9' || c<'0'){if(c=='-')f=-1;c=getchar();}
	while(c>='0' && c<='9'){x=(x<<1)+(x<<3)+(c^48);c=getchar();}
	return x*f;
}
int L[N],R[N],T,n,a[N],st[N][18],lg[N];//2^20>1e6
int query(int l,int r){
	return min(st[l][lg[r-l+1]],st[r-(1<<lg[r-l+1])+1][lg[r-l+1]]);
}
stack<int>s;
void solve(){
	n=read();for(int i=1;i<=n;i++)a[i]=read(),st[i][0]=a[i];
	while(!s.empty())s.pop(); 
	for(int i=1;i<=n;i++){
		while(!s.empty() && a[s.top()]<a[i])s.pop();
		if(s.empty())L[i]=0;
		else L[i]=s.top(); 
		s.push(i);
	}
	while(!s.empty())s.pop();
	for(int i=n;i>=1;i--){
		while(!s.empty() && a[s.top()]<a[i])s.pop();
		if(s.empty())R[i]=n+1;
		else R[i]=s.top(); 
		s.push(i);
	}
	lg[1]=0;for(int i=2;i<=n;i++)lg[i]=lg[i>>1]+1;
	for(int i=1;i<=17;i++)
		for(int j=1;j<=n;j++)
			st[j][i]=min(st[j][i-1],st[j+(1<<i-1)][i-1]);
			
	int sum=0;
	for(int l2=1;l2<=n;l2++){
		int l1=L[l2]+1;
		int r1=R[l2];if(r1>n)continue;
		int l=r1,r=n,r2=-1;
		while(l<=r){
			int mid=(l+r)>>1;
			if(query(r1,mid)>=a[l2]){
				r2=mid;
				l=mid+1;
			}
			else r=mid-1;
		}
		sum+=(l2-l1+1)*(r2-r1+1);
	}
	int assist=0;
	for(int i=1;i<n;i++)assist+=i*(n-i);
	printf("%lld\n",assist-sum);
	return ;
}
signed main(){
	T=read();while(T--)solve();
	return 0;
}