题目

点这里看题目。

分析

容易看出,将序列分割并且递归,其实并不会影响排序过程。它影响的是 work_counter 的值——如果分割出了长度为 1 的序列,那么这个序列里面的值将不再贡献。而分割出长度为 1 的序列,其实就相当于某个元素到了它应该到的位置,并且在此之后不会被挪动。我们称这个元素成为了一个“分割点”。

不妨先考虑 \(a\) 全部不相等的情况,这时可以放心地对序列进行离散化。那么对于一个元素单独考虑,经过了若干轮冒泡之后,如果没有以它结尾的逆序对,同时它到了该到的地方,那么它就不会继续贡献了。注意这个条件按冒泡轮数具有单调性,因此我们可以二分它什么时候成为“分割点”。

此时我们需要解决的问题是,求出若干轮冒泡排序后,某个元素的位置。这是经典问题,设 \(b_i\) 为由 \(i\) 结尾的逆序对个数,则若排序轮数 \(x\le b_i\),元素 \(i\) 只会向前平移 \(x\) 个位置。对于 \(x>b_i\) 的元素,它们已经“松动”,会从大到小、从后往前依次填入还没有被占据的位置。

因此,当我们对于元素 \(i\),检验第 \(t\) 轮排序结束后是否成为“分割点”时,一定会有 \(t\ge b_i\)。当 \(t=b_i\) 时直接检查,而当 \(t>b_i\) 的时候,需要算出此时它在“松动”的元素中的排名,再从此时的空位中找出它的位置,这两个操作都可以用主席树维护,因此可以 \(O(\log n)\) 检查。这样我们得到了 \(O(n\log^2n)\) 的算法。

如果 \(a\) 中有重复元素,我们同样可以离散化,只不过同样大小的元素离散化后从前往后应该是递增的。这样做是合理的,因为冒泡排序是稳定的排序算法,相同大小的元素不会交换前后位置。


当然,我们还有更优的算法。对于元素 \(a_x\),我们可以直接根据序列的情况讨论出它经过几轮后会变成“分割点”:

  1. \(a_x\) 大的数,这些数必须在 \(a_x\) 之前才会影响到它。这些数总共会导致 \(a_x\) 需要等待 \(b_x\) 轮才会松动。换句话说,如果 \([1,x]\)\(x\) 是第 \(k\) 大,那么它至少需要 \(k-1\) 轮冒泡排序。
  2. \(a_x\) 小的数,这些数必须在 \(a_x\) 之后才会影响到它。对于任何一个 \(x<y,a_x>a_y\),如果 \([1,y]\)\(x\) 是第 \(k\) 大,则它至少需要 \(k\) 轮才能越过 \(y\)。显然对于固定的 \(x\),我们只需要考虑最靠右的 \(y\) 带来的影响。

可以发现,两个情况取 \(\max\) 就是 \(a_x\) 最终成为“分割点”的时间;并且第一种情况实际上是第二种情况中不存在 \(y\) 的特例,因此两种情况可以合并起来写。

用树状数组实现即可。复杂度为 \(O(n\log n)\)

小结:

  1. 注意一下对于排序算法的稳定性的运用,这个平时用得比较少;
  2. 多练习一下对于问题的分析,而不要总是尝试去“算”,去“求”,太“暴力”了吧
代码
#include <cstdio>
#include <vector>
#include <utility>
#include <assert.h>
#include <iostream>
#include <algorithm>

#define rep( i, a, b ) for( int i = (a) ; i <= (b) ; i ++ )
#define per( i, a, b ) for( int i = (a) ; i >= (b) ; i -- )

typedef long long LL;

const int MAXN = 1e5 + 5, MAXS = 2e6 + 5;

template<typename _T>
void read( _T &x )
{
	x = 0; char s = getchar(); int f = 1;
	while( ! ( '0' <= s && s <= '9' ) ) { f = 1; if( s == '-' ) f = -1; s = getchar(); }
	while( '0' <= s && s <= '9' ) { x = ( x << 3 ) + ( x << 1 ) + ( s - '0' ), s = getchar(); }
	x *= f;
}

template<typename _T>
void write( _T x )
{
	if( x < 0 ) putchar( '-' ), x = -x;
	if( 9 < x ) write( x / 10 );
	putchar( x % 10 + '0' );
}

std :: vector<int> num[MAXN];

int BIT[MAXN], big[MAXN];

int val[MAXN], app[MAXN], tot = 0;

int A[MAXN], pos[MAXN];
int N;

inline void Up( int &x ) { x += ( x & ( -x ) ); }
inline void Down( int &x ) { x -= ( x & ( -x ) ); }
inline void Update( int x, int v ) { for( ; x <= N ; Up( x ) ) BIT[x] += v; }
inline int Query( int x ) { int ret = 0; for( ; x ; Down( x ) ) ret += BIT[x]; return ret; }

namespace Sequence
{
	int emp[MAXS], lch[MAXS], rch[MAXS];
	int rt[MAXN]; int ntot = 0;
	
	inline void Upt( const int x ) { emp[x] = emp[lch[x]] + emp[rch[x]]; }
	
	inline void Copy( const int a, const int b )
	{
		emp[a] = emp[b], lch[a] = lch[b], rch[a] = rch[b];
	}
	
	int Update( const int x, const int l, const int r, const int p )
	{
		int cur = ++ ntot; Copy( cur, x );
		if( l == r ) { emp[cur] ++; return cur; }
		int mid = ( l + r ) >> 1;
		if( p <= mid ) lch[cur] = Update( lch[x], l, mid, p );
		else rch[cur] = Update( rch[x], mid + 1, r, p );
		Upt( cur ); return cur;
	}
	
	int Query( const int x, const int l, const int r, const int rnk )
	{
		if( l == r ) return l;
		int mid = ( l + r ) >> 1;
		if( rnk <= emp[rch[x]] ) return Query( rch[x], mid + 1, r, rnk );
		return Query( lch[x], l, mid, rnk - emp[rch[x]] );
	}

	void Build()
	{
		rep( i, 0, N - 1 )
		{
			rt[i] = rt[i - 1];
			rep( j, 0, ( int ) num[i].size() - 1 )
				rt[i] = Update( rt[i], 1, N, num[i][j] );
		}
	}

	int Query( const int t, const int x ) { return Query( rt[t - 1], 1, N, x ); }
}

namespace Number
{
	int cnt[MAXS], lch[MAXS], rch[MAXS];
	int rt[MAXN]; int ntot = 0;
	
	inline void Upt( const int x ) { cnt[x] = cnt[lch[x]] + cnt[rch[x]]; }
	
	inline void Copy( const int a, const int b )
	{
		cnt[a] = cnt[b], lch[a] = lch[b], rch[a] = rch[b];
	}

	int Update( const int x, const int l, const int r, const int p )
	{
		int cur = ++ ntot; Copy( cur, x );
		if( l == r ) { cnt[cur] ++; return cur; }
		int mid = ( l + r ) >> 1;
		if( p <= mid ) lch[cur] = Update( lch[x], l, mid, p );
		else rch[cur] = Update( rch[x], mid + 1, r, p );
		Upt( cur ); return cur;
	}
	
	int Query( const int x, const int l, const int r, const int segL, const int segR )
	{
		if( ! x ) return 0;
 		if( segL <= l && r <= segR ) return cnt[x];
 		int mid = ( l + r ) >> 1, ret = 0;
 		if( segL <= mid ) ret += Query( lch[x], l, mid, segL, segR );
 		if( mid  < segR ) ret += Query( rch[x], mid + 1, r, segL, segR );
 		return ret;
	}

	void Build()
	{
		rep( i, 0, N - 1 )
		{
			rt[i] = rt[i - 1];
			rep( j, 0, ( int ) num[i].size() - 1 )
				rt[i] = Update( rt[i], 1, N, A[num[i][j]] );
		}
	}
	
	int Query( const int t, const int x ) { return Query( rt[t - 1], 1, N, x, N ) - t; }
}

bool Chk( const int x, const int tim )
{
	if( N - x + 1 <= tim ) return true;
	if( big[x] == tim ) return pos[x] - tim == x;
	int rnk = Number :: Query( tim, x );
	return Sequence :: Query( tim, rnk ) - tim == x;
}

int main()
{
	read( N );
	rep( i, 1, N ) read( A[i] ), val[++ tot] = A[i];
	std :: sort( val + 1, val + 1 + tot );
	bool flg = true;
	rep( i, 1, N )
	{
		int x = std :: lower_bound( val + 1, val + 1 + tot, A[i] ) - val;
		A[i] = x + ( app[x] ++ );
	}
	rep( i, 1, N ) flg &= A[i] == i;
	rep( i, 1, N ) pos[A[i]] = i;
	if( N == 1 ) return puts( "0" ), 0;
	
	rep( i, 1, N ) 
	{
		big[A[i]] = i - 1 - Query( A[i] );
		Update( A[i], 1 );
		num[big[A[i]]].push_back( i );
	}
	
	Sequence :: Build();
	Number :: Build();
	
	LL ans = 0;
	rep( i, 1, N )
	{
		int l = big[i], r = N - 1, mid;
		while( l < r )
		{
			mid = ( l + r ) >> 1;
			if( Chk( i, mid ) ) r = mid;
			else l = mid + 1;
		}
		ans += std :: max( 1, l );
	}
	write( ans ), putchar( '\n' );
	return 0;
}