「CF1637H」Minimize Inversions Number
题目
点这里看题目。
给定一个 \(1\sim n\) 的排列 \(p\)。
你可以进行下列操作正好一次:
- 选定 \(p\) 的一个长度为 \(k\) 的子序列,并将其按照相同的顺序移动到 \(p\) 的最前面。
对于 \(k=0,1,\cdots,n\),分别求出 \(p\) 在操作后的最小逆序对数。
每个测试点包含 \(T\) 组数据。所有数据满足 \(1\leq T\leq 5\times 10^4,1\le n,\sum n\le 5\times 10^5\)。
分析
评价:6。
首先尝试将操作后的逆序对数写出来。设 \(U=\{1,2,3,\dots,n\}\),设 \(S\subseteq\{1,2,3,\dots,n\}\) 为选中的要移动的子序列的下标。则容易发现,仅有 \(i\in U\setminus S,j\in S,i<j\) 这样的 \((i,j)\) 会影响逆序对数。具体地,操作前后逆序对变化量为:
这是因为这样的 \((i,j)\) 会被反序,所以原顺序对贡献 \(+1\),原逆序对贡献 \(-1\)。其它的 \((i,j)\) 对的顺序则保持不变。
改写此式,去掉 \(U\setminus S\)。想到设 \(f_i=\sum_{1\le j<i}[p_j>p_i]\),那么:
设 \(\operatorname{inv} S=\sum_{i,j\in S}[i<j][p_i>p_j]\)。因为存在 \(2\operatorname{inv} S\) 这一项,所以我们即便写出来了 \(\Delta\) 的算式,这个问题也并没有变简单。我们接下来还是要考察最优的 \(S\) 的性质。
尝试对于 \(S\) 进行调整。想要让 \(\Delta\) 不变大,则调整至少要满足以下几个之一:
-
\(2\operatorname{inv} S\) 变小。(但是控制逆序对其实不很容易)
-
\(\sum_{i\in S}i-1\) 变小,也就是把某个 \(i\) 变小。
-
\(\sum_{i\in S}f_i\) 变大。
这一部分可能就没什么处理技术了,只能手动试一试。
例如,如果要让 \(\sum_{i\in S} f_i\) 变大的话,可以考虑将 \(i\in S\) 替换为某个满足 \(j>i,p_i>p_j\) 的下标 \(j\in U\setminus S\)(因为这样 \(i\) 替换为 \(j\) 后 \(f\) 一定变大)。为了便于分析,我们可以钦定 \(j\) 为满足此条件的最小的一个。那么,分析 \(\Delta\) 的变化:
-
考虑 \(k<i\) 的部分的“贡献”:
-
若 \(p_k<p_j\),no change。
-
若 \(p_j<p_k<p_i\),则 \(f\) 会变大,而 \(\operatorname{inv} S\) 有可能变大,不过这并不影响它给 \(\Delta\) 带来的贡献为非正。
-
若 \(p_k>p_i\),no change。
-
-
考虑 \(k=i\) 的贡献:根据 \(\sum_{i\in S}i-1\),它自带 \(+1\) 贡献。其次 \(f\) 会增大 \(1\)。所以总贡献为负。
-
考虑 \(i<k<j\) 的部分的“贡献”。首先,根据 \(\sum_{i\in S}i-1\),它们会自带一个 \(+1\) 的贡献。其次:
-
若 \(p_k<p_i\),则 \(k\in S\)。此时 \(\operatorname{inv} S\) 一定会减去 \(1\),而 \(f\) 可能会增大 \(1\),总的来说贡献肯定为负。
-
若 \(p_k>p_i\),则 \(f\) 一定会增大 \(1\)。如果 \(k\in S\),则 \(\operatorname{inv} S\) 还会增大 \(1\)。
看起来贡献好像是正的,但是我们可以重新选取 \(j\) 使得不存在这样的 \(k\),那么就不必考虑这种情况了。
-
-
考虑 \(j<k\) 的部分的“贡献”:此时只有 \(\operatorname{inv} S\) 可能变化,显然 \(\operatorname{inv}S\) 只会变小。
(其它的方向我没有尝试过 😦)
这一部分分析可以推导出如下结论:一定存在一种最优的 \(S\),满足若 \(i\in S\),则 \(\forall i<j,p_i>p_j\),\(j\) 也 \(\in S\)。
仔细想想,这个结论可以帮助我们消去 \(\operatorname{inv} S\)。设 \(g_i=\sum_{i<j\le n}[p_i>p_j]\),则可以发现 \(\operatorname{inv} S\le \sum_{i\in S}g_i\)。而结论告诉我们,一定存在最优解使得等号成立,于是我们可以直接用较松的形式:
容易发现,现在这个问题其实可以用“最小权闭合子图”的方法做。这也暗示了另一点:虽然上述结论限制了 \(S\) 的形态,但是它的限制并不强,以至于可能的 \(S\) 依然有很多种。
于是,进一步研究 \(w_i=i-1-2f_i+2g_i\) 的性质。尝试继续在已有的经验上考虑:如果有 \(i<j,p_i>p_j\),那 \(w_i,w_j\) 的关系如何?类似于前面“摊开贡献”的分析,我们又可以发现 \(w_i>w_j\)。
结合上述结论,我们发现,一种最优的 \(S\) 就是按照 \(w\) 从小到大取 \(|S|\) 个。于是就可以 \(O(n\log n)\) 解决了。
代码
#include <bits/stdc++.h>
#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 = 5e5 + 5;
template<typename _T>
inline void Read( _T &x ) {
x = 0; char s = getchar(); bool f = false;
while( s < '0' || '9' < s ) { f = s == '-', s = getchar(); }
while( '0' <= s && s <= '9' ) { x = ( x << 3 ) + ( x << 1 ) + ( s - '0' ), s = getchar(); }
if( f ) x = -x;
}
template<typename _T>
inline void Write( _T x ) {
if( x < 0 ) putchar( '-' ), x = -x;
if( 9 < x ) Write( x / 10 );
putchar( x % 10 + '0' );
}
int perm[MAXN], cnt[MAXN];
int wei[MAXN];
int N;
inline void Down( int &x ) { x &= x - 1; }
inline void Up( int &x ) { x += x & ( - x ); }
inline void Update( int x, int v ) { for( ; x <= N ; Up( x ) ) cnt[x] += v; }
inline int Query( int x ) { int ret = 0; for( ; x ; Down( x ) ) ret += cnt[x]; return ret; }
inline void Clear() { memset( cnt, 0, ( N + 1 ) << 2 ); }
int main() {
int T; Read( T );
while( T -- ) {
Read( N );
rep( i, 1, N ) Read( perm[i] );
LL inv = 0;
Clear();
rep( i, 1, N ) {
int tmp = i - 1 - Query( perm[i] );
inv += tmp, wei[i] = i - 1 - 2 * tmp;
Update( perm[i], +1 );
}
Clear();
per( i, N, 1 ) {
int tmp = Query( perm[i] );
wei[i] += 2 * tmp;
Update( perm[i], +1 );
}
std :: sort( wei + 1, wei + 1 + N );
rep( i, 0, N ) {
Write( inv - 1ll * i * ( i - 1 ) / 2 ), putchar( " \n"[i == N] );
inv += wei[i + 1];
}
}
return 0;
}