cf1827B2

description

对于一个长度为 \(n\) 且不存在相同元素的序列 $ a_1...a_n (1 \leq n \leq 3e5)$ ,定义美丽值为将其排为有序序列的最小时间花费,排序过程中可以进行任意次如下操作:
每次将一个区间[L,R]排为有序,时间花费R-L
求给定数组的所有子数组(在原数组连续)的美丽值之和

solution

  • 首先考虑给一个区间[L,R]排序的时候,我们进行的操作区间肯定不会重合,否则我们直接选合并后的区间进行一次操作肯定更优
  • 所以我们给[L,R]排序就相当于将其分为若干段,给每段操作排序一次即可(可能存在长度为1的段,该段时间花费为0)
  • 显然想让时间花费最少就要尽量分多段,区间[L,R]的美丽值为R-L-(段数-1)
  • 假设\(L\leq k<R\),发现只有 \(max\{a_{L},...a_k\} < min\{a_{k+1}...a_R\}\) ,才能让 \(k\) 作为一段的结束,所以该区间美丽值为 R-L-(符合条件的k的个数)
  • 现在考虑要求给定序列的所有子数组的美丽值之和,显然可以考虑每个点作为符合条件的k对应的L,R的情况数,但是这样即使预处理st表,复杂度也是 \(n^2\)
  • 所以我们考虑对于 \(i (2\leq i \leq n)\) ,当 \(min\{a_{k+1}...a_R\} = a_i\)时,我们去找对应的(L,k,R)的数量
  • \(k\) 只能\(i\) 左边第一个满足 \(a_k < a_i\) 的位置
  • \(x\)\(k\) 左边第一个满足 \(a_x > a_i\) 的位置, \(L\) 只能在 \([x+1,k]\) 之间取值
  • 类似的,令 \(y\)\(i\) 右边第一个满足 \(a_y < a_i\) 的位置, \(R\) 只能在 \([i,y-1]\) 之间取
  • 所以当前 \(i\) 对于答案的贡献是 \(-(k-x)*(y-i)\)
  • 最后答案再加上对于所有子数组的 \(R-L\) 即可

查询每个数左边或者右边第一个小于自己的数可以单调栈 \(\mathcal{O}(n)\) 预处理
查询左边第一个大于某个特定数可以线段树上二分 单次 \(\mathcal{O}(logn)\) 在线查询

code

cf1827B2
#include<bits/stdc++.h>
#define ll long long
#define L (rt<<1)
#define R (rt<<1|1)
using namespace std;

const int N=3e5+10;
int n,a[N],sta[N],tp,lx[N],rx[N];
int mx[N*4];
ll ans;

void build(int rt,int l,int r)
{
	if(l==r) return mx[rt]=a[l],void();
	int mid=(l+r)/2;
	build(L,l,mid),build(R,mid+1,r);
	mx[rt]=max(mx[L],mx[R]);
}

int Find(int rt,int l,int r,int p,int v)
{
	if(r<=p&&mx[rt]<=v) return -1;
	if(l==r) return l;
	int mid=(l+r)/2;
	if(p<=mid) return Find(L,l,mid,p,v);
	int tmp=Find(R,mid+1,r,p,v);
	if(tmp!=-1) return tmp;
	return Find(L,l,mid,p,v);
}

int main()
{
//	freopen("1.in","r",stdin);
	int cs; cin>>cs;
	while(cs--)
	{
		cin>>n;
		for(int i=1;i<=n;++i) cin>>a[i];
		a[0]=0,sta[tp=1]=0;
		for(int i=1;i<=n;++i)
		{
			while(tp&&a[sta[tp]]>a[i]) --tp;
			lx[i]=sta[tp],sta[++tp]=i;
		}
		a[n+1]=0,sta[tp=1]=n+1;
		for(int i=n;i>=1;--i)
		{
			while(tp&&a[sta[tp]]>a[i]) --tp;
			rx[i]=sta[tp],sta[++tp]=i;
		}
		a[0]=(int)1e9+1;
		build(1,0,n);
		ans=0;
		for(int i=2;i<=n;++i)
		{
			int k=lx[i];
			if(k==0) continue;
			int x=Find(1,0,n,k-1,a[i]),y=rx[i];
			ans-=1ll*(k-x)*(y-i);
		}
		for(int i=1;i<n;++i) ans+=1ll*i*(n-i);
		printf("%lld\n",ans);
	}
	return 0;
}
posted @ 2023-05-15 21:57  liuzhaoxu  阅读(59)  评论(0编辑  收藏  举报