【状压dp】JZOJ_5230 队伍统计

题意

现在有 n n n个人要排成一列,编号为 1 ∼ n 1\sim n 1n
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);
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值