【题解】[SHOI2016]黑暗前的幻想乡

[SHOI2016]黑暗前的幻想乡

少有的自己逐渐发现了容斥思路的题……

题目大意:要求原无向图的生成树个数,满足每条边被建立的公司不同。

\(\text{Solution:}\)

观察到了我们有 \(n-1\) 个公司,所以在题目的限制下,满足条件的生成树恰好所有公司都参与。所以我们不需要考虑每条边了,直接考虑:对当前情况下 \(x\) 个公司参与的生成树个数 。这样只要我们求出对应 \(n-1\) 情况下的答案,那它直接就是答案了。

那么回到原来的问题,为什么直接忽略公司对原图跑 Matrix-Tree 定理不对呢?这样的答案中 包含了许多 \(n-1\) 个公司中的子集。 那么思路就变成了:如何把这些不合法的子集去掉呢?结合 \(n\leq 17\) 的数据范围也就自然想到了状压枚举子集的容斥思路。

仔细思考一下,我们想要枚举一些公司使得它们 每一个 都要参与到生成树中。但实际上这个问题也是困难的,那不妨先搁置一下,来想一想容斥的想法:

显然我们可以用总的减去算重的:枚举参加了多少公司,则:

\[Ans=\sum_{n-1}f_i-\sum_{n-2}f_i+\sum_{n-3}f_i... \]

容斥系数就和枚举到的公司数奇偶性有关系。

那么,这个式子是对于强制了多少公司都参与的情况下成立的,刚刚我们说即使这样算,算到的结果也包含了对应这些公司的子集。那么怎么算呢?

其实按照上面这个式子上去算也是对的,因为在每一步加减的过程中,比当前枚举到的更小的子集都被抵消掉了,不理解的话可以手推一下,由于恰好抵消,所以我们直接这样枚举容斥就是对的。

于是我们就可以套用矩阵树定理了。注意到有重边的情况,由于这种重边一定来自于两个不同的公司,所以我们不能把它删掉,直接加进去就是对的。

顺便再提一句矩阵树定理:基尔霍夫矩阵是用 度数矩阵 减去 邻接矩阵,求出的结果是:所有生成树边权之积的和。

那么求方案数的时候,只需要令乘积为 \(1\to\) 令边权为 \(1\) 即可。

复杂度 \(O(2^n\cdot n^3),\) 跑的还是蛮快的。

#include<bits/stdc++.h>
using namespace std;
const int mod=1e9+7;
const int N=20;
inline int Add(int x,int y){return (x+y)%mod;}
inline int Mul(int x,int y){return 1ll*x*y%mod;}
inline int Dec(int x,int y){return (x-y+mod)%mod;}
inline int Max(int x,int y){return x>y?x:y;}
inline int Min(int x,int y){return x<y?x:y;}
inline int Abs(int x){if(x<0)x=-x;return x;}
inline int qpow(int x,int y){
    int res=1;
    while(y){
        if(y&1)res=Mul(res,x);
        x=Mul(x,x);y>>=1;
    }
    return res;
}
inline int getinv(int x){return qpow(x,mod-2);}
inline int read(){
    int s=0;
    char ch=getchar();
    while(!isdigit(ch))ch=getchar();
    while(isdigit(ch)){
        s=s*10+ch-'0';
        ch=getchar();
    }
    return s;
}
typedef pair<int,int> pr;
#define fi first
#define se second
#define mp make_pair
int n,m[N];
vector<pr>edge[N];
struct Matrix{
    int a[N][N];
    Matrix(){memset(a,0,sizeof a);}
    int MatrixTree(int L){
        int res=1;
        for(int i=L;i<=n;++i){
            int pos=i;
            for(int j=i+1;j<=n;++j)
                if(a[pos][i]<a[j][i])
                    pos=j;
            if(i!=pos)res=Mul(res,mod-1),swap(a[i],a[pos]);
            int div=getinv(a[i][i]);
            res=Mul(res,a[i][i]);
            for(int j=i;j<=n;++j)a[i][j]=Mul(a[i][j],div);
            for(int j=i+1;j<=n;++j){
                div=a[j][i];
                for(int k=i;k<=n;++k)a[j][k]=Dec(a[j][k],Mul(div,a[i][k]));
            }
        }
        for(int i=L;i<=n;++i)res=Mul(res,a[i][i]);
        return res;
    }
};
int f[1<<N];
Matrix GetMatrix(int state){
    Matrix A;
    for(int i=0;i<n-1;++i){
        if(!(state>>i&1))continue;
        for(auto j:edge[i]){
            int u=j.fi;
            int v=j.se;
            A.a[u][u]=Add(A.a[u][u],1);
            A.a[v][v]=Add(A.a[v][v],1);
            A.a[u][v]=Dec(A.a[u][v],1);
            A.a[v][u]=Dec(A.a[v][u],1);
        }
    }
    return A;
}
inline int lowbit(int x){return x&(-x);}
inline int popcount(int x){
    int res=0;
    while(x){
        res++;
        x-=lowbit(x);
    }
    return res;
}
int sum[N],Ans;
int main(){
    n=read();
    for(int i=0;i<n-1;++i){
        m[i]=read();
        for(int j=0;j<m[i];++j){
            int u=read(),v=read();
            edge[i].push_back(mp(u,v));
        }
    }
    for(int i=0;i<(1<<(n-1));++i){
        Matrix A=GetMatrix(i);
        f[i]=A.MatrixTree(2);
    }
    for(int i=0;i<(1<<(n-1));++i){
        int num=popcount(i);
        sum[num]=Add(sum[num],f[i]);
    }
    int opt=(n-1)&1;
    for(int i=n-1;i>=1;--i){
        int op=i&1;
        if(op==opt)Ans=Add(Ans,sum[i]);
        else Ans=Dec(Ans,sum[i]);
    }
    printf("%d\n",Ans);
    return 0;
}
posted @ 2021-08-20 13:40  Refined_heart  阅读(28)  评论(0编辑  收藏  举报