「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)\) 会影响逆序对数。具体地,操作前后逆序对变化量为:

\[\Delta=\sum_{i\in U\setminus S}\sum_{j\in S}[i<j]\left([p_i<p_j]-[p_i>p_j]\right) \]

这是因为这样的 \((i,j)\) 会被反序,所以原顺序对贡献 \(+1\),原逆序对贡献 \(-1\)。其它的 \((i,j)\) 对的顺序则保持不变。

改写此式,去掉 \(U\setminus S\)。想到设 \(f_i=\sum_{1\le j<i}[p_j>p_i]\),那么:

\[\Delta=2\sum_{i,j\in S}[i<j][p_i>p_j]+\sum_{i\in S}(i-1-2f_i)-\binom{|S|}{2} \]

\(\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\)。而结论告诉我们,一定存在最优解使得等号成立,于是我们可以直接用较松的形式:

\[\Delta=\sum_{i\in S}(i-1-2f_i+2g_i)-\binom{|S|}{2} \]

容易发现,现在这个问题其实可以用“最小权闭合子图”的方法做。这也暗示了另一点:虽然上述结论限制了 \(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;
}
posted @ 2023-07-05 20:53  crashed  阅读(82)  评论(0编辑  收藏  举报