这里讲的是
O(3n⋅n)
O
(
3
n
⋅
n
)
的可以过的暴力,正解不会。。。
把问题转化成给了
n
n
个白点,个黑点,给定了
n−1
n
−
1
条白白边和黑黑边,求再填
n
n
条黑白边使之联通的方案数。
先用给定的边缩点,记下缩点后每个大点的,然后随意钦定一个白点当根,剩下就是要求一层黑一层白的填,直接子集DP即可。
具体地,设
FS,c,i
F
S
,
c
,
i
表示当前已经选了
S
S
中的点,当前层颜色是,伸向下一层的边有
i
i
条。只要枚举的一个子集
T
T
,且中元素颜色全为
c
c
,记中元素个数是
cnt
c
n
t
,
size
s
i
z
e
的和是
tot
t
o
t
,那么就有转移:
最后答案还要乘上 ∏i≠rootsizei ∏ i ≠ r o o t s i z e i ,因为一个大点中每个点都可以作为连接上一层父亲的点。
还要减减枝,比如说一种颜色已经选完之后,下一层要立即选择全集什么的。。。就可以水过了。
代码:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define N 45
#define ll long long
#define up(x,y) (x=(x+(y))%mod)
using namespace std;
const int mod=998244353;
int n,m,p[N],fa[N],e[N][2],sz[N],pc[1050000],tot[1050000];
ll f[1050000][2][21],ans,fac[N];
int getfa(int v)
{
if(fa[v]==v) return v;
return (fa[v]=getfa(fa[v]));
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++)
scanf("%d%d",&e[i][0],&e[i][1]);
for(int i=m+1;i<n;i++)
{
scanf("%d%d",&e[i][0],&e[i][1]);
e[i][0]+=n;e[i][1]+=n;
}
fac[0]=1;
for(int i=1;i<=n;i++)
fac[i]=fac[i-1]*i%mod;
for(int i=1;i<=(n<<1);i++)
fa[i]=i;
for(int i=1;i<n;i++)
if(getfa(e[i][0])!=getfa(e[i][1])) fa[fa[e[i][0]]]=fa[e[i][1]];
for(int i=1;i<=(n<<1);i++)
sz[getfa(i)]++;
int cnt=0;
for(int i=1;i<=n;i++)
if(getfa(i)==i) sz[++cnt]=sz[i];
m=cnt;
for(int i=n+1;i<=(n<<1);i++)
if(getfa(i)==i) sz[++cnt]=sz[i];
n=cnt;
f[0][1][sz[n]]=1;n--;
int R[2];R[0]=((1<<m)-1),R[1]=R[0]^((1<<n)-1);
for(int s=0;s<(1<<n);s++)
for(int i=1;i<=n;i++)
if((s>>(i-1))&1) pc[s]++,tot[s]+=sz[i];
for(int s=0;s<(1<<n);s++)
{
int u[2];u[0]=s&R[0],u[1]=s&R[1];
for(int c=0;c<=1;c++)
{
if(u[c^1]==R[c^1]&&s+1!=(1<<n)) continue;
for(int t=u[c];t;t=(t-1)&u[c])
if(f[s^t][c^1][pc[t]])
up(f[s][c][tot[t]-pc[t]],f[s^t][c^1][pc[t]]*fac[pc[t]]);
}
}
for(int c=0;c<=1;c++)
up(ans,f[(1<<n)-1][c][0]);
for(int i=1;i<=n;i++)
ans=ans*sz[i]%mod;
printf("%lld",ans);
return 0;
}