正题
题目链接:https://www.luogu.com.cn/problem/P4336
题目大意
nnn个点,n−1n-1n−1个边集,求有多少种方案使得每个边集中恰好选出一条边使得这nnn个点连成一棵树。
解题思路
我们需要利用好n−1n-1n−1个边集这个性质,因为nnn很小,考虑容斥。
和[ZJOI2016]小星星那题一样,如果对于kkk个边集我们不选,那么就表示有kkk条边是重复的,所以容斥系数就是(−1)k(-1)^k(−1)k。
枚举去掉的边集,然后剩下的用矩阵树求答案即可。
时间复杂度O(2nn3)O(2^nn^3)O(2nn3),实际会小一些
codecodecode
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
#define mp(x,y) make_pair(x,y)
#define ll long long
using namespace std;
const ll N=20,P=1e9+7;
ll n,a[N][N],ans;
vector<pair<ll,ll> >q[N];
ll count(ll x){ll ans=0; while(x){ans++;x-=(x&-x);}return ans;
}
ll power(ll x,ll b){ll ans=1;while(b){if(b&1)ans=ans*x%P;x=x*x%P;b>>=1;}return ans;
}
void solve(ll p){memset(a,0,sizeof(a));for(ll i=1;i<n;i++){if((p>>i-1)&1)continue;for(ll j=0;j<q[i].size();j++){ll x=q[i][j].first,y=q[i][j].second;a[x][y]--;a[y][x]--;a[x][x]++;a[y][y]++;}}ll f=(count(p)&1)?-1:1,prod=1;for(ll i=1;i<n;i++){ll w=i;for(ll j=i;j<n;j++){if(a[i][j]){if(i!=j)f=-f;w=j;break;}}swap(a[i],a[w]);prod=prod*a[i][i]%P;if(!a[i][i])return;ll inv=power(a[i][i],P-2);for(ll j=i;j<n;j++)a[i][j]=a[i][j]*inv%P;for(ll j=i+1;j<n;j++){ll rate=P-a[j][i];for(ll k=i;k<n;k++)a[j][k]=(a[j][k]+rate*a[i][k]%P+P)%P;}}ans=(ans+prod*f+P)%P;return;
}
signed main()
{scanf("%lld",&n);for(ll i=1;i<n;i++){ll m,x,y;scanf("%lld",&m);for(ll j=1;j<=m;j++){scanf("%lld%lld",&x,&y);q[i].push_back(mp(x,y));}}ll MS=(1<<n-1);for(ll i=0;i<MS-1;i++)solve(i);printf("%lld\n",ans);return 0;
}