[CF715E] Complete the Permutations(dp+组合计数)

Posted cyf32768

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[CF715E] Complete the Permutations(dp+组合计数)相关的知识,希望对你有一定的参考价值。

Problem

给定两个长度为 (n) 的排列 (a,b),但是其中有些位置未知,用 (0) 表示。

定义两个排列的距离为:每次选择 (a) 中的两个元素交换,使其变为 (a) 的最小次数。

要求补全两个排列,求补全之后 (a,b) 距离为 (i) ((i∈[0,n-1])) 的方案数。

(n ≤ 250),答案对 (998244353) 取模。

Solution

先考虑怎么算补全之后 (a,b) 的距离:

对于每个 (i) ((i∈[1,n])),连边 (a_i→b_i)

记连边后的图上环的个数为 (m),那么 (a,b) 的距离为 (n-m)

回到原问题:

我们建 (n) 个点 (p_1,p_2,...,p_n) 表示 (n) 个位置,建 (n) 个点 (v_1,v_2,...,v_n) 表示 (1) ~ (n)(n) 个数值。

对于每个 (i) ((i∈[1,n])),如果 (a_i≠0),连边 (v_{a_i}→p_i)。如果 (b_i≠0),连边 (p_i→v_{b_i})。得到一张初始的图。

在这张图中,(v_x→p_y) 表示补全后的 (a_y=x)(p_x→v_y) 表示补全后的 (b_x=y)

只能在 (v_i)(p_j) 之间连边,不许 (v_i)(v_j) 连边,也不许 (p_i)(p_j) 连边。

初始的图中有一些环和链(包括单点成链)。

我们要做的就是加一些边(这些边只能从一条链的结尾连向一条链的开头,可以是同一条链的结尾和开头),使得最终的图有 (x) ((x∈[1,n])) 个环,(0) 条链。

记初始的图中:

环有 (c_0) 个。
(p) 开头,(v) 结尾的链((1) 类链)有 (c_1) 条。
(v) 开头,(p) 结尾的链((2) 类链)有 (c_2) 条。
(p) 开头,(p) 结尾的链((3) 类链)有 (c_3) 条。
(v) 开头,(v) 结尾的链((4) 类链)有 (c_4) 条。

因为环上必须是 (v,p) 交替出现的,所以一个环的组成可以是:

  1. (1) 类链组成。
  2. (2) 类链组成。
  3. (1,2,3,4) 类链组成。
  4. (3,4) 类链组成。
  5. (1,3,4) 类链组成。
  6. (2,3,4) 类链组成。

我们先考虑 (1,2,3) 类链之间怎么连边。

(f_i) 表示满足 (lceil)(i) 个仅 (1) 类链组成的环 ( floor) 的情况下,(1) 类链的连边方案数。

因为 (1) 类链是 (p) 开头 (v) 结尾,所以我们考虑的 (lceil) 连边方案数 ( floor) 也就是给每个 (1) 类链的结尾连一条出边的方案数。

显然这个出边要么连向 (1) 类链的开头,要么连向 (3) 类链的开头。

(lceil) 恰好(i) 个仅 (1) 类链组成的环 ( floor) 的方案数不好算,考虑让 (f_i) 先表示 (lceil) 至少(i) 个仅 (1) 类链组成的环 ( floor) 的方案数。

枚举这 (i)(lceil)(1) 类链组成的环 ( floor) 用了 (j)(1) 类链。

我们要从 (c_1)(1) 类链中选出 (j) 条,把它们排成 (i) 个环。

剩下的 (c_1-j)(1) 类链,要么连向 (1) 类链,要么连向 (3) 类链。显然是不可以连向用来成环的 (j)(1) 类链的,那么就有 (A_{c_1-j+c_3}^{c_1-j}) 种方案。

于是可得递推式:

[f_i=sum_{j=0}^{c_1}C_{c_1}^j×S_j^i×A_{c_1-j+c_3}^{c_1-j}]

其中 (C) 是组合数,(S) 是第一类斯特林数,(A) 是排列数。

然后计算(lceil) 恰好(i) 个仅 (1) 类链组成的环 ( floor) 的方案数:

[f_i-=sum_{j=i+1}^{c_1}f_j×C_j^i]

这样我们就把 (1) 类链的出边(结尾连出去的边)都搞定了。

接下来搞定 (2) 类链的入边。

(1) 类链同理,记 (g_i) 表示满足 (lceil)(i) 个仅 (2) 类链组成的环 ( floor) 的情况下,(2) 类链的连边方案数。跟 (f_i) 计算方法一样。

截至目前,除掉所有的环以及 (4) 类链,有下面 (4) 种长链(((x))(_n) 表示连续若干个 (x)):

  1. ((1) 类链)(_n→) (3) 类链 (→) ((2) 类链)(_n)
  2. ((1) 类链)(_n→) (3) 类链
  3. (3) 类链 (→) ((2) 类链)(_n)
  4. (3) 类链

发现这 (4) 种长链有两个共有的特点:

  1. 只含 (1)(3) 类链。
  2. 开头和结尾一定都是 (p)

把这 (4) 种长链和所有的 (4) 类链一起串成环,我们就完成任务了。

我们令 (h=f×g)

(ans_i) 表示最终的图有 (i) 个环的方案数。

枚举仅由 (1) 类链组成的环、仅由 (2) 类链组成的环共 (j) 个,那么我们要把上述 (4) 种长链和所有的 (4) 类链串成 (i-j) 个环。

因为每条长链里必定只含 (1)(3) 类链,所以我们可以给每条长链分别编号 (1) ~ (c_3)

显然最终形成的环一定是长链和 (4) 类链交替出现。

那么:
[ans_i=sum_{j=0}^ih_j×S_{c_3}^{i-j}*c_4!]

表示将每条长链先分别接 (1)(4) 类链,然后摆成 (i-j) 个环。

摆环的方案数显然是 (S_{c_3}^{i-j})

因为 (c_3=c_4),所以长链和 (4) 类链的连接方案数为 (c_4!)

时间复杂度 (o(n^2))

#include <bits/stdc++.h>

using namespace std;

#define ll long long

template <class t>
inline void read(t & res)
{
    char ch;
    while (ch = getchar(), !isdigit(ch));
    res = ch ^ 48;
    while (ch = getchar(), isdigit(ch))
    res = res * 10 + (ch ^ 48);
}

const int e = 2005, mod = 998244353;
int S[e][e], nxt[e], C[e][e], A[e][e], a[e], b[e], n, ans[e], f[e], g[e], h[e], fac[e];
int c0, c1, c2, c3, c4, deg[e], ret[e];
bool vis[e];

inline int plu(int x, int y)
{
    (x += y) >= mod && (x -= mod);
    return x;
}

inline int sub(int x, int y)
{
    (x -= y) < 0 && (x += mod);
    return x;
}

inline void dfs(int x, bool s, bool t)
{
    vis[x] = 1;
    int y = nxt[x];
    if (y)
    {
        if (vis[y]) c0++;
        else dfs(y, s, t ^ 1);
    }
    else
    {
        if (!s && t) c1++;
        else if (s && !t) c2++;
        else if (s && t) c3++;
        else c4++;
    }
}

inline void init(int cnt, int *f)
{
    int i, j;
    for (i = 0; i <= cnt; i++)
    for (j = 0; j <= cnt; j++)
    f[i] = (f[i] + (ll)C[cnt][j] * S[j][i] % mod * A[cnt - j + c3][cnt - j]) % mod;
    for (i = cnt; i >= 0; i--)
    for (j = i + 1; j <= cnt; j++)
    f[i] = sub(f[i], (ll)f[j] * C[j][i] % mod); 
}

int main()
{
    int i, j;
    read(n);
    for (i = 1; i <= n; i++) read(a[i]);
    for (i = 1; i <= n; i++) read(b[i]);
    for (i = 1; i <= n; i++)
    {
        if (a[i]) nxt[a[i] + n] = i, deg[i]++;
        if (b[i]) nxt[i] = b[i] + n, deg[b[i] + n]++;
    }
    for (i = 1; i <= 2 * n; i++)
    if (!vis[i] && !deg[i]) dfs(i, i > n, i > n);
    for (i = 1; i <= 2 * n; i++)
    if (!vis[i]) dfs(i, i > n, i > n);
    C[0][0] = A[0][0] = S[0][0] = fac[0] = 1;
    for (i = 1; i <= n; i++)
    {
        C[i][0] = A[i][0] = 1;
        fac[i] = (ll)fac[i - 1] * i % mod;
        for (j = 1; j <= i; j++)
        {
            C[i][j] = plu(C[i - 1][j - 1], C[i - 1][j]);
            A[i][j] = (A[i - 1][j] + (ll)A[i - 1][j - 1] * j) % mod;
            S[i][j] = (S[i - 1][j - 1] + (ll)S[i - 1][j] * (i - 1)) % mod;
        }
    }
    init(c1, f); init(c2, g);
    for (i = 0; i <= n; i++)
    for (j = 0; j <= i; j++)
    h[i] = (h[i] + (ll)f[j] * g[i - j]) % mod;
    for (i = 0; i <= n; i++)
    for (j = 0; j <= i; j++)
    ans[i] = (ans[i] + (ll)h[j] * S[c3][i - j] % mod * fac[c4]) % mod;
    for (i = 0; i < n; i++) 
    if (n - i - c0 >= 0) ret[i] = ans[n - i - c0];
    else ret[i] = 0;
    for (i = 0; i < n - 1; i++) printf("%d ", ret[i]);
    printf("%d
", ret[n - 1]);
    return 0;
}

以上是关于[CF715E] Complete the Permutations(dp+组合计数)的主要内容,如果未能解决你的问题,请参考以下文章