2010-1164

从 Trac 迁移的文章

这是从旧校内 Wiki 迁移的文章,可能存在一些样式问题,您可以向 memset0 反馈。

原文章内容如下:

题意:有m种变换ai->bi表示可以把一个ai变成bi。问你n个0到n-1的数组成的序列中有多少种可以通过若干次变换变成0到n-1的排列。其中ai!=aj, bi!=bj。
解法:如果把一个变换看成图中一条边,那么整个图就是每个点入度和出度最多都是1的图。很容易发现每个点要么在一个环里面,要么在一条链上(一个点也算一条链)。对每个部分独立计算最后用乘法原理得到最终结果即可。
{{{
#!cpp
#include <cstdio>
#include <cstring>

typedef long long LL;

const int MAXN = 200;
const LL MOD = 1000000007LL;

int n, m;
int c[201][201];
int e[MAXN];
bool deg[MAXN], used[MAXN];

void init() {
    c[0][0] = 1;
    for (int i = 1; i <= 200; i++) {
        c[i][0] = c[i][i] = 1;
        for (int j = 1; j < i; j++) {
            c[i][j] = c[i - 1][j - 1] + c[i - 1][j];
            (c[i][j] >= MOD) && (c[i][j] -= MOD);
        }
    }
}

int main() {
    init();
    int T;
    scanf("%d", &T);
    for (int cases = 1; cases <= T; cases++) {
        scanf("%d%d", &n, &m);
        memset(e, -1, sizeof(e));
        memset(deg, 0, sizeof(deg));
        memset(used, 0, sizeof(used));
        for (int i = 0; i < m; i++) {
            int x, y;
            scanf("%d%d", &x, &y);
            e[x] = y;
            deg[y] = true;
        }
        LL res = 1LL;
        int tot = n;
        for (int i = 0; i < n; i++) {
            if (!deg[i]) {
                int count = 1;
                used[i] = true;
                for (int j = i; e[j] != -1; j = e[j], used[j] = true, count++);
                for (int j = 1; j < count; j++) {
                    res = res * (count + 1) % MOD;
                }
                res = res * c[tot][count] % MOD;
                tot -= count;
            }
        }
        for (int i = 0; i < n; i++) {
            if (!used[i]) {
                int count = 1;
                used[i] = true;
                for (int j = i; e[j] != i; j = e[j], used[j] = true, count++);
                for (int j = 0; j < count; j++) {
                    res = res * count % MOD;
                }
                res = res * c[tot][count] % MOD;
                tot -= count;
            }
        }
        printf("%lld\n", res);
    }
    return 0;
}
}}}

题意:有m种变换ai->bi表示可以把一个ai变成bi。问你n个0到n-1的数组成的序列中有多少种可以通过若干次变换变成0到n-1的排列。其中ai!=aj, bi!=bj。

解法:如果把一个变换看成图中一条边,那么整个图就是每个点入度和出度最多都是1的图。很容易发现每个点要么在一个环里面,要么在一条链上(一个点也算一条链)。对每个部分独立计算最后用乘法原理得到最终结果即可。

#include <cstdio>
#include <cstring>
typedef long long LL;
const int MAXN = 200;
const LL MOD = 1000000007LL;
int n, m;
int c[201][201];
int e[MAXN];
bool deg[MAXN], used[MAXN];
void init() {
    c[0][0] = 1;
    for (int i = 1; i <= 200; i++) {
        c[i][0] = c[i][i] = 1;
        for (int j = 1; j < i; j++) {
            c[i][j] = c[i - 1][j - 1] + c[i - 1][j];
            (c[i][j] >= MOD) && (c[i][j] -= MOD);
        }
    }
}
int main() {
    init();
    int T;
    scanf("%d", &T);
    for (int cases = 1; cases <= T; cases++) {
        scanf("%d%d", &n, &m);
        memset(e, -1, sizeof(e));
        memset(deg, 0, sizeof(deg));
        memset(used, 0, sizeof(used));
        for (int i = 0; i < m; i++) {
            int x, y;
            scanf("%d%d", &x, &y);
            e[x] = y;
            deg[y] = true;
        }
        LL res = 1LL;
        int tot = n;
        for (int i = 0; i < n; i++) {
            if (!deg[i]) {
                int count = 1;
                used[i] = true;
                for (int j = i; e[j] != -1; j = e[j], used[j] = true, count++);
                for (int j = 1; j < count; j++) {
                    res = res * (count + 1) % MOD;
                }
                res = res * c[tot][count] % MOD;
                tot -= count;
            }
        }
        for (int i = 0; i < n; i++) {
            if (!used[i]) {
                int count = 1;
                used[i] = true;
                for (int j = i; e[j] != i; j = e[j], used[j] = true, count++);
                for (int j = 0; j < count; j++) {
                    res = res * count % MOD;
                }
                res = res * c[tot][count] % MOD;
                tot -= count;
            }
        }
        printf("%lld\n", res);
    }
    return 0;
}