题意
现在有nnn个人要排成一列,编号为1∼n1\sim n1∼n 。
有mmm条矛盾关系(u,v)(u,v)(u,v),表示编号为uuu的人想要排在编号为vvv的人前面。
要使得队伍和谐,最多不能违背kkk条矛盾关系(即不能有超过kkk条矛盾关系(u,v)(u,v)(u,v),满足最后vvv排在了uuu前面)。问有多少合法的排列。答案对1e9+71e9+71e9+7取模。
思路
nnn的范围很小,最大才有202020,考虑状态压缩dpdpdp。
设f[S][i]f[S][i]f[S][i]为当前选了的人集合为SSS,已经违背了iii条关系。
如果我们暴力转移,那么时间复杂度为O(2nn2k)O(2^nn^2k)O(2nn2k)
由于关系是没有重复的,我们可以预处理一下违背的转移条件,设behind[i]behind[i]behind[i]为原来应该站在iii后面的人,在转移时计算出behind[i] & Sbehind[i]\ \&\ Sbehind[i] & S中111的个数,代表有这么多本来应站在后面的人站在前面了。
时间复杂度降成O(2nkn)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);
}
本文介绍了一种使用状态压缩动态规划解决特定排列问题的方法,该问题涉及在考虑到一系列矛盾关系约束下,寻找所有可能的排列组合,同时不超过允许违背的矛盾关系数量。通过预处理违背条件并利用__builtin_popcount()函数,算法将时间复杂度降低到O(2^nkn),适用于小范围的n值。
860

被折叠的 条评论
为什么被折叠?



