题目描述
bobo has a permutation of .
Knowing extra constraints of form , bobo wanna count the number of different permutations modulo .
It is guaranteed that there is at least one such permutation.
给出一个全排列中满足的若干约束,让你求在这个约束下满足条件的排列个数。
约定
分析
PART 1 转化
我们可以把一个数看成一个点,对于一个约束关系就从一个点到另一个点连一条有向边。由于题目保证有解,我们会得到一个DAG(不一定全部连通,是若干个连通块组成的)。那么我们的问题就变成了对于这若干个连通块,求它们的拓扑排序计数。
PART 2 计数
由于是对一个图的计数,还会涉及到图上一些复杂的关系,我们并不方便直接推出式子计算。考虑 较小,那么对于一个连通块最多是20条边,21个点,我们可以使用状态压缩的方式,计算已经安排好集合 中的点的方案数(保证 合法)。那么对每个连通块这样计数,最后再进行合并。
参考程序
// 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);
}
总结
这道题的转化十分巧妙。转化之后对于计数的处理也需要较高的技巧,一环紧扣一环,是一道好题。回想当年的多校,题面还是那么简洁清晰,不想现在一堆废话还有语法错误。