其中 n ≤ 20 n\le 20 n≤20
qwq由于这个数据范围,不难想到状压 d p dp dp
我们令 f [ s ] f[s] f[s]表示只考虑 s s s集合中的点的拓扑序(也就是 s s s中的点已经在拓扑序里),贡献总和是多少。
考虑从 s s s转移到 s ∣ ( 1 < < i ) s | (1<<i) s∣(1<<i)
对于
i
i
i这个点来说,只能保留出发点是
s
s
s中的边,其他边都不存在,才能够合法地加入拓扑序,那么这个点的贡献,我们预处理一个
i
n
[
j
]
in[j]
in[j]表示能到
j
j
j的点集。
那么每次新加入一个点,他的贡献就是
f
[
s
]
∗
(
1
<
<
y
m
h
[
i
n
[
j
]
与
s
]
)
f[s]*(1<<ymh[in[j] 与 s])
f[s]∗(1<<ymh[in[j]与s])
其中与表示按位与
ymh数组是当前状态的1的个数。
表示合法的边是出发点在
s
s
s中的边,而这些边每一条都是可以存在可以不存在的。
所以是2的次方。
这样计算之所以是正确的,是因为,每一条边的贡献,只会在他的出点那里计算一遍,由于计算的时候, s s s都是不同的。
所以说贡献不会重复,也不会遗漏。
直接上代码
// luogu-judger-enable-o2
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<vector>
#include<queue>
#include<cmath>
#include<map>
#include<set>
#define pb push_back
#define mk make_pair
#define ll long long
#define lson ch[x][0]
#define rson ch[x][1]
#define rint register int
using namespace std;
inline int read()
{
int x=0,f=1;char ch=getchar();
while (!isdigit(ch)) {if (ch=='-') f=-1;ch=getchar();}
while (isdigit(ch)) {x=(x<<1)+(x<<3)+ch-'0';ch=getchar();}
return x*f;
}
const int maxn = 3e6+1e2;
const int mod = 998244353;
int in[maxn],ymh[maxn];
ll f[maxn];
int n,m;
int mx;
inline int lowbit(int x)
{
return x & (-x);
}
signed main()
{
n=read(),m=read();
mx = 1<<n;
for (rint i=1;i<=m;++i)
{
int u=read(),v=read();
in[v]|=(1<<(u-1));
}
for (int i=1;i<=mx;i++) ymh[i]=ymh[i-lowbit(i)]+1;
f[0]=1;
for (rint i=0;i<mx;++i)
{
for (rint j=1;j<=n;++j)
{
if ((1<<(j-1)) & i) continue;
f[i|(1<<(j-1))] = ( f[i | (1<<j-1)] + f[i] * 1ll*(1ll <<ymh[in[j] & i]))%mod;
}
}
cout<<f[mx-1];
return 0;
}