HDU4917 Permutation

题目描述

bobo has a permutation p_1,p_2,…,p_n of 1,2,…,n.
Knowing m extra constraints of form p_{a_i} < p_{b_i}, bobo wanna count the number of different permutations modulo 10^9+7.
It is guaranteed that there is at least one such permutation.

给出一个全排列中满足的若干约束,让你求在这个约束下满足条件的排列个数。

约定

1\leq n\leq 40,0\leq m\leq 20

分析

PART 1 转化

我们可以把一个数看成一个点,对于一个约束关系就从一个点到另一个点连一条有向边。由于题目保证有解,我们会得到一个DAG(不一定全部连通,是若干个连通块组成的)。那么我们的问题就变成了对于这若干个连通块,求它们的拓扑排序计数。

PART 2 计数

由于是对一个图的计数,还会涉及到图上一些复杂的关系,我们并不方便直接推出式子计算。考虑m较小,那么对于一个连通块最多是20条边,21个点,我们可以使用状态压缩的方式,计算已经安排好集合S中的点的方案数(保证S合法)。那么对每个连通块这样计数,最后再进行合并。

参考程序

// vjudge 244023 F
// HDU4917
#include <cstdio>
#include <cstring>
typedef long long LL;
const int MAXN = 45;
const int MAXS = (1 << 21) + 5;
const int MOD = 1000000007;
struct Edge {
    int to, next;
} E[MAXN];

int C[MAXN][MAXN], tote, N, M, last[MAXN], fa[MAXN], mark[MAXN], comp[MAXN], prv[MAXN], DP[MAXS];

inline void add_edge(int u, int v) { E[++tote].to = u, E[tote].next = last[v], last[v] = tote; }
inline void plus(int & x, int d) { x = (x + d) % MOD; }
int find(int x) {
    if (fa[x] == x) return x;
    return fa[x] = find(fa[x]);
}
void init();
void solve();

int main() {
    init();
    while (scanf("%d%d", &N, &M) == 2) solve();
    return 0;
}

void init() {
    int i, j;
    for (C[0][0] = i = 1; i <= 40; i++)
        for (C[i][0] = j = 1; j <= i; j++) C[i][j] = (C[i - 1][j - 1] + C[i - 1][j]) % MOD;
}

void solve() {
    memset(last, 0, sizeof(last));  // 邻接表初始化
    int i, j, k, stat, lim_stat, ai, bi, f1, f2;
    for (i = 1; i <= N; i++) fa[i] = i;
    for (i = tote = 0; i < M; i++) {
        scanf("%d%d", &ai, &bi);
        add_edge(ai, bi);
//      并查集维护连通块信息
        f1 = find(ai), f2 = find(bi);
        if (f1 != f2) fa[f1] = f2;
    }
    int res = 1, tot = N, size;
//  res记录结果,tot存储排列上剩下可供选择的位置,size存储当前连通块的大小
    for (i = 1; i <= N; i++)
        if (fa[i] == i) {
            for (j = 1, size = 0; j <= N; j++)
                if (find(j) == i) mark[j] = size, comp[size++] = j;     // mark记录点对应连通块中的相对编号
            if (size <= 2) { res = (LL)res * C[tot][size] % MOD; tot -= size; continue; }   // size小于等于2那么只有1中方案
            for (j = 0; j < size; j++)
                for (prv[j] = 0, k = last[comp[j]]; k; k = E[k].next) prv[j] |= 1 << mark[E[k].to]; // 预处理出必须先遍历的前驱,压缩
            for (DP[0] = 1, stat = 1, lim_stat = 1 << size; stat < lim_stat; ++stat)
                for (DP[stat] = 0, j = 0; j < size; j++)
                    if (stat >> j & 1 && prv[j] == (prv[j] & (stat & ~(1 << j)))) plus(DP[stat], DP[stat & ~(1 << j)]);    // 这里注意不要漏了==后的括号!==优先级高于&
            res = (LL)res * C[tot][size] % MOD * DP[lim_stat - 1] % MOD;
            tot -= size;
        }
    printf("%d\n", res);
}

总结

这道题的转化十分巧妙。转化之后对于计数的处理也需要较高的技巧,一环紧扣一环,是一道好题。回想当年的多校,题面还是那么简洁清晰,不像现在一堆废话还有语法错误。

About The Author

发表评论

电子邮件地址不会被公开。 必填项已用*标注