Codeforces 2145D Inversion Value of a Permutation 题解 [ 绿 ] [ 背包 ] [ 正难则反 ] [ 构造 ]

Inversion Value of a Permutation:场上被这个 D 卡了 1h,结果往后一看 EF 是板子 /ll/ll/ll。

直接构造不好做,先考虑怎么计算符合条件的区间。

观察逆序对的性质,显然只有极近的逆序对才对计数会有影响。具体而言,如果 \((p_a, p_b)\) 是一个逆序对且满足 \(a + 1 < b\),那么一定存在一个 \(|a - b|\) 更小的逆序对,被夹在 \(a\sim b\) 之间。证明是容易的,考虑枚举中间夹着的那个数,发现它无论比 \(p_a\) 大、夹在 \(p_a, p_b\) 之间、比 \(p_b\) 小,都会产生一个额外的、更小的逆序对。因此所有影响计数的逆序对都是相邻的逆序对

由上面的结论,我们可以知道,如果任意一个区间,包含了至少一个这种 \(a + 1 = b\) 的相邻逆序对,那么就一定会被计数。但是“至少一个”的条件依然不是很好刻画,于是考虑正难则反的套路,计数被转化为不包含任意一个此类逆序对的区间数。同时可以发现 \(k\) 的限制也能很好地转化成新问题的限制,直接令 \(k'\gets \frac{n(n-1)}{2} - k\) 即可。

把这些相邻的逆序对在序列中标出,容易发现这些逆序对把序列划分成了若干个段(在 \(a\)\(a + 1\) 之间分割)。例如 \(1, 2, 3, 5, 4, 7, 6\) 被分割为了 \(1, 2, 3, 5 ; 4, 7 ; 6\) 三段。而单独的一段产生的贡献显然是 \(\frac{len(len - 1)}{2}\)

会了对区间计数之后,剩下的就是容易的了。我们考虑用若干个段去拼成整个序列,之后跑一个背包 DP 即可。具体地,定义 \(dp_{i, j}\) 表示用了 \(i\) 个数,区间数为 \(j\) 是否可行,转移的时候记录一下前驱即可。最后从末状态倒过来构造,时间复杂度 \(O(tn^4)\)。需要特判长度为 \(1\) 的段。

实际上本题还有优化空间。注意到长度为 \(\bm1\) 的段贡献为 \(\bm0\),所以对于 \(k\) 固定的情况,只需要保证 \(\bm n\) 最小,后面补一些长度为 \(1\) 的段就能成功构造。所以可以省略背包的第一维,定义 \(dp_i\) 表示当前有 \(i\) 个区间,\(n\) 的最小值,并记录一下转移前驱。这是普通的 01 背包,且与单个测试点中的 \(n, k\) 无关,所以时间复杂度 \(O(tn + n^3)\)

#include <bits/stdc++.h>
#define fi first
#define se second
#define eb(x) emplace_back(x)
#define pb(x) push_back(x)
#define lc(x) (tr[x].ls)
#define rc(x) (tr[x].rs)
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef long double ldb;
using pi = pair<int, int>;
const int N = 35, V = 1005, MXN = 30, MXV = 435;
ll n, k, a[N];
ll dp[V], pre[V];
void init()
{
    memset(dp, 0x3f, sizeof(dp));
    dp[0] = 0;
    for(int i = 2; i <= MXN; i++)
    {
        ll v = i * (i - 1) / 2;
        for(int j = v; j <= MXV; j++)
        {
            if(dp[j] > dp[j - v] + i)
            {
                dp[j] = dp[j - v] + i;
                pre[j] = i;
            }
        }
    }
}
void solve()
{
    cin >> n >> k;
    k = n * (n - 1) / 2 - k;
    if(dp[k] > n)
    {
        cout << "0\n";
        return;
    }
    if(k == 0)
    {
        for(int i = n; i >= 1; i--) cout << i << " ";
        cout << "\n";
        return;
    }
    int nowx = n, nowy = k;
    vector<int> ans;
    while(nowy)
    {
        int tmp = pre[nowy];
        nowx -= tmp;
        nowy -= tmp * (tmp - 1) / 2;
        ans.push_back(tmp);
    }
    sort(ans.begin(), ans.end());
    nowx++;
    for(int i = n - nowx + 1, j = n; i <= n; i++, j--) a[i] = j;
    for(int i = 1; i <= n - nowx; i++) a[i] = i;
    int now = 0;
    for(int i = 0; i < ans.size() - 1; i++)
    {
        now += ans[i];
        swap(a[now], a[now + 1]);
    }
    for(int i = 1; i <= n; i++) cout << a[i] << " ";
    cout << "\n";
}
int main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);
    init();
    int t;
    cin >> t;
    while(t--) solve();
    return 0;
}
posted @ 2025-10-07 21:13  KS_Fszha  阅读(60)  评论(0)    收藏  举报