题解:深黯「军团」

题解:深黯「军团」

前情提要:不建议任何人写此做法,除非你想受虐。。。

题目大意:求一个排列字典序向后 \(k\) 个的逆序对和,\(n\le 5e5,k \le 1e18\)

思路

这东西一看就是数位 DP 一类的,直接开始搓。

可是数位 DP,首先第一个问题就是,如何求出字典序向后第 \(k\) 个排列,这个就有说法了。

首先考虑试填,但是当卡到这个排列的下界时贡献时不同的,所以可以设 \(g_i\) 表示到 \(i\) 位并且 \(i\) 位不动的方案数。

\(len_i\) 表示 \([i+1,n]\) 中,比 \(a_i\) 大的数的个数。

那么很容易就可以发现 \(g_i=g_{i+1}+len_{i+1}(n-i-1)!\)

\(g_{i+1}\) 表示后一个卡到下界的方案数,\(len_{i+1}(n-i-1)!\) 则是后面的数没有了束缚,自由的选取。

那么这样每次判断如果 \(k\le g_{i}\) 那么就说明这个排列的 \([1,i]\) 一定没有被动,设这个分界点为 \(pos\)

接下来考虑怎么找到剩下的数。

首先在 \(pos\) 这个位置时,是要特殊算一遍 \(g_{pos}\) 的,这个特判一下就好了。

然后一般情况就是从 1 一直加加加,加到那个 \(k \le (n-i)!\)

因为现在没有束缚了,所以可以一直加加加。

但是这样是错的,为啥?因为在加的过程中会遇到已经被占用的数,所以需要判断如果这个数没被占用才能认为是加一(就是没被占用的数中的第 \(k\) 小)。

现在如果这个 \(k\) 在第一次循环的散块中,那么就已经求完了。

那么 \(k\) 非常大咋办?

很好办,发现每 \(n!\) 都会进行一个循环,所以直接模 \(n!\) 即可。

然后剩下的 \(k\) 和上面一样处理就好。

这样我们就求出了第 \(k\) 个排列是什么,设为 \(b\)

然后考虑差分答案。

如果是散块加整块的话,后面直接跑,然后前面的块进行一个差分。

差分形式就是 \(calc(b) - calc(a - 1)\)

这个 \(a-1\) 可以直接上 \(next_permutation\),不过也有更小常数做法。

现在来考虑怎么求 \(calc\)

这个东西本质就是一个 \([1,2,3,4,5,6,\cdots, n]\)\(b\) 的一个前缀和。

可以继续试填。

假设当前枚举到了 \(i\),当前数为 \(p\)

则答案可以分为 5 个部分。

第一部分是前面已经钦定的贡献,第二部分是后面的贡献,第三部分是前面对后面的贡献,第四部分是 \(p\) 对前面的贡献,第五部分是 \(p\) 对后面的贡献。

假设访问表示出现在钦定部分的数。

因为要保证复杂度,所以要将所有与的 \(p\) 放到一块计算。

先看第一部分,只需要设 \(pre\) 为当前钦定的数的逆序对个数即可。

第二部分发现是一个长度固定的全排列(可以直接离散化),所以直接预处理为 \(h_i\)

第三部分比较恶心,注意一件事情,就是无论后面怎么变,这个贡献是固定的,因为相对位置没有改变,那么可以直接设 \(low_i\) 为当前没有出现在钦定部分的比 \(a_i\) 小的数的个数,再设 \(e=\sum_{j=1}^{i}{low_j}\),这个 \(e\) 就是这部分的贡献。

第四部分发现根本没用,因为在 \(e\) 里已经算过对前面的贡献,所以直接并入 \(e\) 就好了。

第五部分十分恶心,可以发现如果设 \(sum_i\) 表示在未访问过的数中比 \(a_i\) 小的数(本身也未访问),那么容易发现这一部分就是 \(x=\sum_{j=1}^{n}{sum_j} (j未访问)\)

现在来把式子整理出来就是

\[low_i\times h_{n-i}+(pre+e)low_i(n-i)!+x(n-i)! \]

这个式子请自行意会。

然后考虑加入一个数进入钦定会发生什么。

首先 \(pre\) 的变化很简单。

然后因为一开始将第四部分的贡献并入了 \(e\) 中,但是这不符合现在的值了,所以要减去错误(这个数已经被访问了)的在加上 \(low_i\)

再就是 \(x\) 的变化,因为这个点已经访问所以 \(sum_i=0\),然后从 \([a_i,n]\) 这所有的数,前面都会少一个数,所以都需要 - 1。

接下来只剩下一个东西了,就是咋求 \(h_i\),容易发现,如果从小到大枚举下一个数,这个数可以任意选择位置,然后贡献就是这个位置后面的数的个数,因为这个数是最大值。

这样就算完了!

分析一下复杂度竟然是 \(O(n^2)\) 的!!!!!!!!!(自行证明)

这咋办???????

优化

首先一开始那个 \(len\) 可以直接上一颗树状数组搞定。

然后那个为出现的第 \(k\) 小可以用线段树二分或平衡树搞定。

然后在 \(calc\) 的时候,\(pre\)\(e\) 的变化可以使用树状数组搞定。

但是这个 \(sum\) 就需要使用线段树维护区间内值和未访问点的数量(自行思考)

这样下来复杂度就是 \(O(n\log n)\),常数我不想说了,太大了,估计在 \(20\) 以上。

代码

粘个代码,想抄直接抄吧。

using I = int;
#define int long long
using ll = __int128;

int k;
int mod, n, a[N], b[N];
ll fac[N], g[N], f[N], p[N];
bool vis[N];

class AarryTree
{
    public :

    int c[N];

    void Update(int pos, int k)
    {
        for (int i = pos; i <= n; i += (i & (-i))) c[i] += k;
    }

    int QueryPoint(int pos)
    {
        int ans = 0;
        for (int i = pos; i; i -= (i & (-i))) ans += c[i];
        return ans;
    }

    int Query(int l, int r)
    {
        return QueryPoint(r) - QueryPoint(l - 1);
    }
}AT;

#define lid id << 1
#define rid id << 1 | 1

class SemTree
{
    public :

    int val[N << 2], tag[N << 2];
    I cnt[N << 2];

    void PushDown(I id)
    {
        if (tag[id])
        {
            tag[lid] += tag[id], tag[rid] += tag[id];
            val[lid] += cnt[lid] * tag[id], val[rid] += cnt[rid] * tag[id];
            tag[id] = 0;
        }
    }

    void PushUp(I id)
    {
        val[id] = val[lid] + val[rid];
        cnt[id] = cnt[lid] + cnt[rid];
    }

    void Build(I id, I l, I r)
    {
        val[id] = tag[id] = 0;
        if (l == r) return cnt[id] = 1, val[id] = l - 1, void();
        I mid = (l + r) >> 1;
        Build(lid, l, mid), Build(rid, mid + 1, r);
        PushUp(id);
    }

    void UpdatePoint(I id, I l, I r, I pos)
    {
        if (l == r) return val[id] = cnt[id] = tag[id] = 0, void();
        PushDown(id);
        I mid = (l + r) >> 1;
        if (pos <= mid) UpdatePoint(lid, l, mid, pos);
        else UpdatePoint(rid, mid + 1, r, pos);
        PushUp(id);
    }

    void Update(I id, I cl, I cr, I l, I r, int k)
    {
        if (l <= cl && cr <= r) return tag[id] += k, val[id] += cnt[id] * k % mod, void();
        I mid = (cl + cr) >> 1;
        PushDown(id);
        if (l <= mid) Update(lid, cl, mid, l, r, k);
        if (r > mid) Update(rid, mid + 1, cr, l, r, k);
        PushUp(id);
    }

    int Query(I id, I cl, I cr, I l, I r)
    {
        if (l > r) return 0;
        if (l <= cl && cr <= r) return val[id];
        I mid = (cl + cr) >> 1;
        PushDown(id);
        int ans = 0;
        if (l <= mid) ans = Query(lid, cl, mid, l, r);
        if (r > mid) ans += Query(rid, mid + 1, cr, l, r);
        return ans;
    }
}ST;

class FHQ
{
    public :

    I ls[N], rs[N], val[N], rnk[N], tot, cnt[N], root;

    I New(I x) {++tot, ls[tot] = rs[tot] = 0, val[tot] = x, rnk[tot] = rand(), cnt[tot] = 1; return tot;}

    void PushUp(I id) {cnt[id] = cnt[ls[id]] + cnt[rs[id]] + 1;}

    void Split(I id, I &x, I &y, I k)
    {
        if (!id) return (x = 0, y = 0), void();
        if (val[id] > k)
        {
            y = id;
            Split(ls[y], x, ls[y], k);
        }
        else
        {
            x = id;
            Split(rs[x], rs[x], y, k);
        }
        PushUp(id);
    }

    I Merge(I x, I y)
    {
        if (!x || !y) return x | y;
        if (rnk[x] > rnk[y])
        {
            rs[x] = Merge(rs[x], y);
            PushUp(x);
            return x;
        }
        else
        {
            ls[y] = Merge(x, ls[y]);
            PushUp(y);
            return y;
        }
    }

    void Insert(I v)
    {
        I x, y;
        Split(root, x, y, v);
        // cerr << (ll)y << ' ';
        I z = Merge(x, New(v));
        root = Merge(z, y);
    }

    void Del(I v)
    {
        I x, y, z;
        Split(root, x, y, v - 1);
        Split(y, y, z, v);
        root = Merge(x, z);
    }

    I Kth(I k)
    {
        I x = root;
        while (1)
        {
            if (k == cnt[ls[x]] + 1) break;
            if (k <= cnt[ls[x]]) x = ls[x];
            else k -= cnt[ls[x]] + 1, x = rs[x];
        }
        return x;
    }
}T;

ll solve(int *a, bool op)
{
    memset(vis, 0, sizeof(vis));
    memset(AT.c, 0, sizeof(AT.c));
    ST.Build(1, 1, n);
    ll ans = 0, pre = 0, low = 0, e = 0, x = 0;
    for (int i = 1; i <= n; i++)
    {
        low = 0, x = 0;
        low += a[i] - 1 - AT.Query(1, a[i]);
        x = ST.Query(1, 1, n, 1, a[i] - 1);
        (ans += p[n - i] * low + low * fac[n - i] % mod * (pre + e) + x * fac[n - i]) %= mod;
        int tmp = AT.Query(a[i], n);
        (pre += tmp) %= mod, e -= tmp;
        ST.Update(1, 1, n, a[i], n, -1);
        ST.UpdatePoint(1, 1, n, a[i]);
        (e += low) %= mod;
        AT.Update(a[i], 1);
    }
    return (ans + pre * op) % mod;
}

ll prefac[N];

signed main()
{
    freopen("data.in", "r", stdin); freopen("data.out", "w", stdout);
    freopen("army.in", "r", stdin); freopen("army.out", "w", stdout);
    ios :: sync_with_stdio(false), cin.tie(0), cout.tie(0);

    cin >> n >> k >> mod;
    for (int i = 1; i <= n; i++) cin >> a[i];
    fac[0] = 1, prefac[0] = 1;
    for (ll i = 1; i <= n; i++)
    {
        fac[i] = fac[i - 1] * i % mod;
        prefac[i] = prefac[i - 1] * i;
        if (prefac[i] > k) prefac[i] = k + 10;
    }
    for (int i = 2; i <= n; i++) p[i] = (i * p[i - 1] + (i * (i - 1) / 2ll) % mod * fac[i - 1]) % mod;
    ll pos = 0, tmp = 1;
    g[n] = 1;
    memset(AT.c, 0, sizeof(AT.c));
    for (int i = n - 1; i >= 0; i--)
    {
        ll len = 0;
        len = AT.Query(a[i + 1], n);
        tmp += len * prefac[n - i - 1];
        if (tmp >= k) {pos = i + 1; break;}
        g[i] = tmp;
        vis[a[i + 1]] = 1;
        AT.Update(a[i + 1], 1);
    }
    if (pos)
    {
        memset(vis, 0, sizeof(vis));
        memset(AT.c, 0, sizeof(AT.c));
        for (int i = 1; i <= n; i++) T.Insert(i);
        for (int i = 1; i < pos; i++) T.Del(a[i]), vis[a[i]] = 1, AT.Update(a[i], 1), b[i] = a[i];
        for (int i = pos; i <= n; i++)
        {
            ll tmp = 1;
            if (i == pos)
            {
                int len = 0;
                len = a[i] - AT.Query(1, a[i]);
                tmp = len + 1;
                k -= g[i];
            }
            tmp += k / prefac[n - i];
            if (k % prefac[n - i] == 0) tmp--, k = prefac[n - i];
            else k %= prefac[n - i];
            tmp = T.Kth(tmp);
            b[i] = tmp;
            vis[tmp] = 1;
            AT.Update(a[i], 1);
            T.Del(tmp);
        }
    // return 0;
        cout << (int)(solve(b, 1) - solve(a, 0) + mod) % mod << '\n';
        return 0;
    }
    ll ans = p[n] - solve(a, 0), w = 0; // first
    pos = 0;
    k -= g[0];
    w += k / prefac[n];
    if (k % prefac[n] == 0) w--, k = prefac[n];
    else k %= prefac[n];
    (ans += p[n] * w % mod) %= mod;

    for (int i = 1; i <= n; i++) a[i] = i;
    tmp = 1;
    g[n] = 1;
    memset(vis, 0, sizeof(vis));
    memset(AT.c, 0, sizeof(AT.c));
    for (int i = n - 1; i >= 0; i--)
    {
        int len = 0;
        len = AT.Query(a[i + 1], n);
        tmp += len * prefac[n - i - 1];
        if (tmp >= k) {pos = i + 1; break;}
        g[i] = tmp;
        vis[a[i + 1]] = 1;
        AT.Update(a[i + 1], 1);
    }
    memset(vis, 0, sizeof(vis));
    memset(AT.c, 0, sizeof(AT.c));
    for (int i = 1; i <= n; i++) T.Insert(i);
    for (int i = 1; i < pos; i++) T.Del(a[i]), vis[a[i]] = 1, AT.Update(a[i], 1), b[i] = a[i];
    for (int i = pos; i <= n; i++)
    {
        int tmp = 1;
        if (i == pos)
        {
            int len = 0;
            len = a[i] - AT.Query(1, a[i]); // maybe O(1)
            tmp = len + 1;
            k -= g[i];
        }
        tmp += k / prefac[n - i];
        if (k % prefac[n - i] == 0) tmp--, k = prefac[n - i];
        else k %= prefac[n - i];
        tmp = T.Kth(tmp);
        b[i] = tmp;
        vis[tmp] = 1;
        AT.Update(a[i], 1);
        T.Del(tmp);
    }
    cout << (int)(ans + solve(b, 1)) % mod << '\n';

    return 0;
}
posted @ 2025-08-17 06:51  QEDQEDQED  阅读(58)  评论(4)    收藏  举报