JZOJ 5230 队伍统计

最近状压写少了看到这题第一反应是拓扑排序?
数据特征居然没看出来……

发现 \(n \le 20\),状压可以承受,所以设 \(f_{S,i}\) 表示选择的集合是 \(S\),其中有 \(i\) 对矛盾的排列数。
转移显然,了解状压套路的很容易想到。

但是有一个问题:转移时新产生的矛盾数要怎么做?
暴力的话,复杂度会炸。
考虑再利用状压,令 \(back_i\) 表示本来应该排在 \(i\) 后面的数的集合。
转移时,新产生的矛盾数就是 \(\lvert S \bigcap back_i \rvert\)
统计 \(1\) 的个数可以预处理,但是我偷懒用了黑科技 __builtin_popcount

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include <cstdio>
#include <vector>
#define popcnt __builtin_popcount
using namespace std;
const int N = 20;
const int K = 20;
const long long mod = 1e9 + 7;
int n,m,k,full;
int back[N + 5];
long long f[(1 << N) + 10][K + 5],ans;
int main()
{
freopen("count.in","r",stdin);
freopen("count.out","w",stdout);
scanf("%d%d%d",&n,&m,&k);
full = (1 << n) - 1;
int u,v;
for(register int i = 1;i <= m;++i)
scanf("%d%d",&u,&v),back[u] |= (1 << v - 1);
f[0][0] = 1;
for(register int i = 0;i <= full;++i)
for(register int j = 1;j <= n;++j)
if(!(i & (1 << j - 1)))
for(register int l = 0;l + popcnt(back[j] & i) <= k;++l)
f[i | (1 << j - 1)][l + popcnt(back[j] & i)] = (f[i | (1 << j - 1)][l + popcnt(back[j] & i)] + f[i][l]) % mod;
for(register int i = 0;i <= k;++i)
ans = (ans + f[full][i]) % mod;
printf("%lld\n",ans);
}