点这里看题目。
分析容易看出,将序列分割并且递归,其实并不会影响排序过程。它影响的是 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\),我们可以直接根据序列的情况讨论出它经过几轮后会变成“分割点”:
- 比 \(a_x\) 大的数,这些数必须在 \(a_x\) 之前才会影响到它。这些数总共会导致 \(a_x\) 需要等待 \(b_x\) 轮才会松动。换句话说,如果 \([1,x]\) 中 \(x\) 是第 \(k\) 大,那么它至少需要 \(k-1\) 轮冒泡排序。
- 比 \(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)\)。
小结:
- 注意一下对于排序算法的稳定性的运用,这个平时用得比较少;
- 多练习一下对于问题的分析,而不要总是尝试去“算”,去“求”,太“暴力”了吧
#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;
}