@description@

给定两个排列 p, q,他们中的有些位置被替换成了 0。

两个排列 p, q 的距离为:最少需要在 p 中执行交换两个元素的操作,使得 p, q 相等。

对于每个 0 <= k <= n,求有多少将 0 替换回正整数并满足 p, q 依然是排列的替换方法,使得 p, q 距离为 k。

原题戳这里

@solution@

考虑两个排列的距离怎么算。如果把 p[i] -> q[i] 看成一个置换,就是将置换排序最少需要的交换次数。
这是个经典问题,答案为 (n - 该置换含有的循环数量)。

考虑一种特殊情况:如果给定完整的 p 而 q 全零,实际上就是求 n 个数划分成 n-k 个循环的方案数,即第一类斯特林数。
因此本题跟第一类斯特林数或多或少有些勾连。

通过 p, q 中已经给定的数,可以把现在已有的循环求出来。剩余的一定会形成若干条链。
直接拿现有的链去作第一类斯特林数?貌似不行。考虑一个例子:p = {1, 0}, q = {0, 2},此时 1 与 2 不可能在同一循环。

形象化地描述,假如 p[i] = a 且 q[i] = 0,可以理解为 a 的下面凸,否则认为 a 的下面凹;同理,假如有 p[j] = 0 且 q[j] = b,可以理解为 b 的上面凸,否则认为 b 的上面凹。
则如果可以 a->b 应该满足 a 的下面与 b 的上面应该为一凹一凸。

一个链中间的东西对链的转移没有限制,只有两个端点会产生限制。
假如一个链形如 a -> ... -> b,则这条链上面的凹凸性取决 a,下面的凹凸性取决 b。
因此一条链的限制可以描述为上下的凹凸性,我们可以将所有链分为 4 类。

为了避免出现更多的分类讨论,我们假设 p[i] = 0, q[i] = 0 的 i 为上下都凸的链(理解成 p[i] = n + i, q[i] = 0 与 p[n + i] = 0, q[n + i] = n + i 也行)。

那么问题转成了给定 4 类链的数量,求将这些链划分为 p 个循环的方案数。
回到我们一开始的想法:这种题与第一类斯特林数有点关系。因此类比第一类斯特林数的递推式子进行 dp。

斯特林数的递推式子:每次加入一个数,要么单独成一个循环,要么接在前面已经加入的某个数的后面。
先把不是上下都凹的全部求第一类斯特林数。但可能会出现不合法情况:凸凸相接或凹凹相接。
凸凸相接直接把上下都凹的当作润滑剂塞进去,凹凹相接没办法,只能在 dp 中限制。
所以 dp 时不让上凹下凸的接在上凸下凹的后面即可。其他和斯特林数一样。

时间复杂度貌似是 O(n^2)?

@accepted code@

#include <cstdio>
const int MAXN = 250;
const int MOD = 998244353;
int f[MAXN + 5], g[MAXN + 5], c[4];
void solve() {
    int p = 0; f[0] = 1;
    for(int i=1;i<=c[0];i++) f[0] = 1LL*f[0]*i%MOD;
    for(int i=1;i<=c[3];i++) {
        for(int j=0;j<=p;j++) g[j] = f[j], f[j] = 0;
        p++;
        for(int j=1;j<=p;j++) f[j] = (1LL*g[j]*(i-1)%MOD + g[j-1]) % MOD;
    }
    for(int i=1;i<=c[2];i++) {
        for(int j=0;j<=p;j++) g[j] = f[j], f[j] = 0;
        p++;
        for(int j=1;j<=p;j++) f[j] = (1LL*g[j]*(c[3]+i-1)%MOD + g[j-1]) % MOD;
    }
    for(int i=1;i<=c[1];i++) {
        for(int j=0;j<=p;j++) g[j] = f[j], f[j] = 0;
        p++;
        for(int j=1;j<=p;j++) f[j] = (1LL*g[j]*(c[3]+i-1)%MOD + g[j-1]) % MOD;
    }
}
int a[MAXN + 5], b[MAXN + 5], n;
int u[MAXN + 5], d[MAXN + 5], p[MAXN + 5];
bool vis[MAXN + 5];
int main() {
    scanf("%d", &n);
    for(int i=1;i<=n;i++) scanf("%d", &a[i]), u[a[i]] = i;
    for(int i=1;i<=n;i++) scanf("%d", &b[i]), d[b[i]] = i;
    for(int i=1;i<=n;i++)
        if( u[i] ) p[i] = b[u[i]];
    for(int i=1;i<=n;i++)
        if( !d[i] || !a[d[i]] ) {
            int x = i;
            while( p[x] )
                vis[x] = true, x = p[x];
            vis[x] = true;
            if( !d[i] ) {
                if( !u[x] ) c[0]++;
                else c[1]++;
            }
            else {
                if( !u[x] ) c[2]++;
                else c[3]++;
            }
        }
    int cnt = 0;
    for(int i=1;i<=n;i++) {
        if( vis[i] ) continue;
        int x = i;
        do {
            vis[x] = true;
            x = p[x];
        }while( x != i );
        cnt++;
    }
    for(int i=1;i<=n;i++)
        if( !a[i] && !b[i] ) c[3]++;
    solve();
    for(int i=n;i>=cnt&&i>=1;i--)
        printf("%d ", f[i-cnt]);
    for(int i=cnt-1;i>=1;i--) printf("%d ", 0);
}

@details@

貌似官方给的是 fft 做法?

话说我也不知道我是怎么想到这么“形象”的理解的。
感觉“形象”到有点抽象了。

12-22 19:06