[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\) 类链组成的环 \(\rfloor\) 的情况下,\(1\) 类链的连边方案数。

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

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

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

枚举这 \(i\) 个 \(\lceil\) 仅 \(1\) 类链组成的环 \(\rfloor\) 用了 \(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\) 类链组成的环 \(\rfloor\) 的方案数:

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

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

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

和 \(1\) 类链同理,记 \(g_i\) 表示满足 \(\lceil\) 有 \(i\) 个仅 \(2\) 类链组成的环 \(\rfloor\) 的情况下,\(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\n", ret[n - 1]);
    return 0;
}

原文地址:https://www.cnblogs.com/cyf32768/p/12196047.html

时间: 2024-10-12 02:30:59

[CF715E] Complete the Permutations(dp+组合计数)的相关文章

3.29省选模拟赛 除法与取模 dp+组合计数

LINK:除法与取模 鬼题.不过50分很好写.考虑不带除法的时候 其实是一个dp的组合计数. 考虑带除法的时候需要状压一下除法操作. 因为除法操作是不受x的大小影响的 所以要状压这个除法操作. 直接采用二进制状压是不明智的 2的个数最多为13个 2^13也同样到达了1e4的复杂度. 考虑 hash状压 即 2的个数有x个 那么我们就有状态w表示2还有x个. 这样做的原因是把一些相同的东西给合并起来 而并非分散开来.即有多个2直接记录有多少个即可. 可以发现 这样做不同的除数最多只有5个 状态量较

swust oj 2516 教练我想学算术 dp+组合计数

#include<stdio.h> #include<string.h> #include<iostream> #include<string> #include<queue> #include<cmath> #include<map> #include<algorithm> #include<vector> //#define debug using namespace std; const in

BZOJ 2302: [HAOI2011]Problem c [DP 组合计数]

2302: [HAOI2011]Problem c Time Limit: 30 Sec  Memory Limit: 256 MBSubmit: 648  Solved: 355[Submit][Status][Discuss] Description 给n个人安排座位,先给每个人一个1~n的编号,设第i个人的编号为ai(不同人的编号可以相同),接着从第一个人开始,大家依次入座,第i个人来了以后尝试坐到ai,如果ai被占据了,就尝试ai+1,ai+1也被占据了的话就尝试ai+2,……,如果一直

HDU 4359——Easy Tree DP?——————【dp+组合计数】

Easy Tree DP? Time Limit: 10000/5000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others)Total Submission(s): 1460    Accepted Submission(s): 557 Problem Description A Bear tree is a binary tree with such properties : each node has a value o

HDU 4359 Easy Tree DP?(是dp但并不是tree dp + 组合计数)

HDU 4359 题意:定义Bear Tree为一颗二叉树,这种二叉树每个结点有一个权值,范围在2^0~2^n-1,并且每个值只用一次,对于每个结点,如果同时存在左右子树,那么左子树的权值和要小于右子树的权值和.求点数为N,层次为D的Bear Tree的数量. 思路: 2^0 + 2^1 + ... + 2^n < 2^(n+1) 根据这个性质,我们可以得出权值最大节点必须在右子树上,并且只要同时存在左右子树,则将权值最大节点放在右子树上就一定符合条件. 所以我们用dp[i][j]表示点数为i且

HDU 4832 组合计数dp

Chess Time Limit: 6000/3000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others) Total Submission(s): 509    Accepted Submission(s): 198 Problem Description 小度和小良最近又迷上了下棋.棋盘一共有N行M列,我们可以把左上角的格子定为(1,1),右下角的格子定为(N,M).在他们的规则中,"王"在棋盘上的走法遵循十字

[ZJOI2010]排列计数 (组合计数/dp)

[ZJOI2010]排列计数 题目描述 称一个1,2,...,N的排列P1,P2...,Pn是Magic的,当且仅当2<=i<=N时,Pi>Pi/2. 计算1,2,...N的排列中有多少是Magic的,答案可能很大,只能输出模P以后的值 输入输出格式 输入格式: 输入文件的第一行包含两个整数 n和p,含义如上所述. 输出格式: 输出文件中仅包含一个整数,表示计算1,2,?, 的排列中, Magic排列的个数模 p的值. 输入输出样例 输入样例#1: 20 23 输出样例#1: 16 说明

Yue Fei&#39;s Battle(组合计数递推)

//求一个直径为 k 的树有多少种形态,每个点的度不超过 3 // 非常完美的分析,学到了,就是要细细推,并且写的时候要细心 还有除法取模需要用逆元 #include <iostream> #include <stdio.h> #include <string.h> #include <math.h> #include <stdlib.h> using namespace std; #define MOD 1000000007 #define L

bzoj 1004 Cards 组合计数

这道题考察的是组合计数(用Burnside,当然也可以认为是Polya的变形,毕竟Polya是Burnside推导出来的). 这一类问题的本质是计算置换群(A,P)中不动点个数!(所谓不动点,是一个二元组(a,p),a∈A,p∈P ,使得p(a)=a,即a在置换p的作用后还是a). Polya定理其实就是告诉了我们一类问题的不动点数的计算方法. 对于Burnside定理的考察,我见过的有以下几种形式(但归根结底还是计算不动点数): 1.限制a(a∈A)的特点,本题即是如此(限制了各颜色个数,可以