Loading

CF1827B2 Range Sorting (Hard Version) 题解

*CF1827B2 Range Sorting (Hard Version)

一.思路:

按题意去找似乎很麻烦。但是注意到,我们若对每个区间都暴力的花费\(r-l\)的代价去修改所有区间,总代价为

\[\sum_{l=1}^{n}\sum_{r=l}^n(r-l)=\frac{1}{2}\sum_{l=1}^{n}(n-l+1)\times(n-l)=\frac{n(n+1)(n-1)}{6} \]

这就提醒我们反向来思考,发现我们如果将一段区间拆成两段处理,并且依旧可以变得有序,代价会少\(1\),而且两次操作区间与区间之间不相交一定最优,所以我们的目标就是要找到所有可以这样拆开的区间来减少我们花费的代价。可以证明的是,满足这样的条件的区间需要满足的充要条件是:

\[\exists x\in[l+1,r],\max_{k=l}^{x-1}a_k<\min_{k=x}^{r}a_k \]

即前半段最大值小于后半段最小值。

我们发现直接模拟这个性质复杂度属于是直接爆了,所以换个思路,我们假定某一个位置\(i\)\(a_i\)是某个右半区间\([x,r]\)的最小值,那么\([l,x-1]\)的最大值要小于\(a_i\)

现在也就需要统计有多少区间\([l,r]\)可以拆成两半,并且一定有\(i\in[x,r]\)

那么我们找到\(i\)后面第一个比\(i\)小的数\(a_{minr}\),所以\(r\)可以是区间\([x,minr)\)中的任意整数。接着我们找到\(i\)前面第一个比\(a_i\)小的数\(a_{tmin}\),根据定义,这个数只能放在左半边区间,且作为左右区间分界点,也就是下标\(tmin=x-1\)。最后,从\(tmin\)往前找第一个比\(a_i\)大的数\(a_{maxl}\),所以\(l\)可以是区间\((maxl,tmin]\)中的任意一个数。

因为每拆一次少\(1\)的代价,所以最后来自这些区间的贡献就为:

\[(minr-i)\times(tmin-maxl) \]

现在有一个关键问题:如何快速处理找前后的第一个\(\max/\min\)呢?其实处理方式有很多,这里说一下我个人的方法:相信找数这个问题我们都不陌生,很容易想到二分查找,但是问题在于这道题的序列是无序的,没有单调性,所以我们理应要去构造一些新东西来给二分check。可能没什么头绪,那我们不妨从头思考,为什么我们的复杂度高呢?显然的,这是因为我们每一次都要去扫描前面的数然后去打擂台处理,我们希望有一种办法可以快速高效的查询一段区间的最值,所以第一反应是线段树或者树状数组,事实上,这是可行的,但是别忘了,我们的序列是静态的,我们有更加快速的查询方法,即:我会ST表!我们尝试着将ST表与二分进行结合,每一次查询起始点到\(mid\)之间d的最值,结合当前的\(a_i\)的大小对数级别复杂度缩小每一次的查询范围,最后得到结果

二.code:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define LL long long
inline LL read() {
	int x=0,f=1;char ch=getchar();
	while(ch > '9' || ch < '0'){if(ch == '-'){f = -1;}ch = getchar();}
	while(ch >= '0'&&ch <= '9'){x = x * 10 + ch - 48; ch = getchar();}
	return x * f;
} 

const int N = 1e6 + 10;
const int LOg_2 = 20;
LL n,ans;
int perm[N],st_min[N][LOg_2],st_max[N][LOg_2];
int l2[N];

void init()
{
	int lim = l2[n];
	for(int j = 1;j <= lim;++j)
	{
		int add_up = (1 << j) - 1,add_up2 = (1 << (j - 1));
		for(int i = 1;i + add_up - 1 <= n;++i)
		{
			st_min[i][j] = std::min(st_min[i][j - 1],st_min[i + add_up2][j - 1]);
			st_max[i][j] = std::max(st_max[i][j - 1],st_max[i + add_up2][j - 1]);
		}
	}
}

int query_min(int l,int r)
{
	int k = l2[r - l + 1];
	return std::min(st_min[l][k],st_min[r - (1 << k) + 1][k]);
}

int query_max(int l,int r)
{
	int k = l2[r - l + 1];
	return std::max(st_max[l][k],st_max[r - (1 << k) + 1][k]);
}

int main()
{
	n = read();
	for(int i = 1;i <= n;++i) 
	{
		perm[i] = read();
		st_min[i][0] = st_max[i][0] = perm[i];	
	}
	ans = (n * (n + 1) / 2) * (n - 1) / 3;
	l2[0] = -1;
	for(int i = 1;i <= n;++i) l2[i] = l2[i >> 1] + 1;
	init();
	
	for(int i = 1;i <= n;++i)
	{
		int ll = 0,rl = 0,rr = n + 1;
		int l = i + 1,r = n;
		while(l <= r)
		{
			int mid = (l + r) >> 1;
			if(query_min(i + 1,mid) < perm[i])
				r = mid - 1,rr = mid;
			else 
				l = mid + 1;
		}
		l = 1,r = i - 1;
		while(l <= r)
		{
			int mid = (l + r) >> 1;
			if(query_min(mid,i - 1) < perm[i])
				l = mid + 1,rl = gmid;
			else
				r = mid - 1; 
		}
		l = 1,r = rl - 1;
		while(l <= r)
		{
			int mid = (l + r) >> 1;
			if(query_max(mid,rl - 1) > perm[i])
				l = mid + 1,ll = mid;
			else
				r = mid - 1;
		}
		ans -= 1ll * (rr - i) * (rl - ll); 
	}
	std::cout << ans;
	return 0;
}
posted @ 2025-03-05 08:25  AxB_Thomas  阅读(21)  评论(0)    收藏  举报
Title