题意
现在有
n
n
n个人要排成一列,编号为
1
∼
n
1\sim n
1∼n 。
有
m
m
m条矛盾关系
(
u
,
v
)
(u,v)
(u,v),表示编号为
u
u
u的人想要排在编号为
v
v
v的人前面。
要使得队伍和谐,最多不能违背
k
k
k条矛盾关系(即不能有超过
k
k
k条矛盾关系
(
u
,
v
)
(u,v)
(u,v),满足最后
v
v
v排在了
u
u
u前面)。问有多少合法的排列。答案对
1
e
9
+
7
1e9+7
1e9+7取模。
思路
n n n的范围很小,最大才有 20 20 20,考虑状态压缩 d p dp dp。
设 f [ S ] [ i ] f[S][i] f[S][i]为当前选了的人集合为 S S S,已经违背了 i i i条关系。
如果我们暴力转移,那么时间复杂度为 O ( 2 n n 2 k ) O(2^nn^2k) O(2nn2k)
由于关系是没有重复的,我们可以预处理一下违背的转移条件,设 b e h i n d [ i ] behind[i] behind[i]为原来应该站在 i i i后面的人,在转移时计算出 b e h i n d [ i ] & S behind[i]\ \&\ S behind[i] & S中 1 1 1的个数,代表有这么多本来应站在后面的人站在前面了。
时间复杂度降成 O ( 2 n k n ) O(2^nkn) O(2nkn)。
代码
#include<cstdio>
const int P = 1e9 + 7;
int n, m, k;
int f[1048576][21], behind[21];
int main() {
scanf("%d %d %d", &n, &m, &k);
for (int i = 1, u, v; i <= m; i++) {
scanf("%d %d", &u, &v);
behind[u] |= 1 << v - 1;
}
int t = 1 << n;
f[0][0] = 1;
for (int i = 0; i < t; i++)
for (int s = 0; s <= k; s++)
if (f[i][s])
for (int j = 1; j <= n; j++) {
if (i & 1 << j - 1) continue;
int need = __builtin_popcount(behind[j] & i);//自带函数计算1的个数,最好手打
if (need + s > k) continue;
f[i | 1 << j - 1][s + need] = (f[i | 1 << j - 1][s + need] + f[i][s]) % P;
}
int ans = 0;
for (int i = 0; i <= k; i++)
ans = (ans + f[(1 << n) - 1][i]) % P;
printf("%d", ans);
}