BZOJ 4455: [Zjoi2016]小星星

我称之为补题解,感觉这可以帮助我把之前做过的没写题解的一些题目(好吧坑了好多题目)都补起来

首先容易想到\(O(n!\times n)\)的大暴力,然后套路地发现在树上可以化为子集问题

我们设\(f_{i,j,k}\)表示\(i\)的子树内,\(i\)映射为\(j\)之后且所有点映射完后构成了图上的点集\(k\)(状压)的方案数,显然我们可以在枚举一个子节点的同时枚举父节点的所有状态,然后再对于它的状态补集枚举子集即可,复杂度应该是\(O(3^n\times n)\)

然而这样无法通过此题,我们细细分析以下会发现现在的复杂度瓶颈主要是在枚举子集上,考虑如果我们能去掉后面那一维该有多好

考虑我们假设映射可以重复,那么显然此时会多算出一部分,比如映射过去的集合比全集少了一些元素

很直观地,我们考虑再减去少了一个元素的方案数,然后此时少了两个元素的又被多减了需要再加回去……

想到了什么,容斥啊!我们可以对映射过去的集合进行容斥,每次只把点映射到这个集合里去,然后算出此时的方案数即可

那么此时的方程显然就是:

\[f_{x,j}=\prod_{y\in son(x)} \sum_{j=1}^n f_{y,j}\times g_{i,j}\]

最后\(\sum_{i=1}^n f_{1,i}\)就是方案数,根据集合的元素取正负号即可(代码是一年前的了233):

#include<cstdio>
#include<vector>
#define RI register int
using namespace std;
const int N=20;
int n,m,x,y,status,id[N],cnt; bool p[N][N];
vector <int> v[N]; long long ans,f[N][N];
inline void DFS(int now,int fa)
{
    RI i,j,k; int lim=v[now].size(),to;
    for (i=0;i<lim;++i) if ((to=v[now][i])!=fa) DFS(to,now);
    for (i=1;i<=cnt;++i)
    {
        for (f[now][id[i]]=1,j=0;j<lim;++j)
        if ((to=v[now][j])!=fa)
        {
            long long ret=0; for (k=1;k<=cnt;++k)
            if (p[id[i]][id[k]]) ret+=f[to][id[k]];
            f[now][id[i]]*=ret;
        }
    }
}
int main()
{
    RI i,j; for (scanf("%d%d",&n,&m),i=1;i<=m;++i) scanf("%d%d",&x,&y),p[x][y]=p[y][x]=1;
    for (i=1;i<n;++i) scanf("%d%d",&x,&y),v[x].push_back(y),v[y].push_back(x);
    for (status=(1<<n)-1,i=0;i<=status;++i)
    {
        for (cnt=j=0;j<n;++j) if ((i>>j)&1) id[++cnt]=j+1;
        DFS(1,0); for (j=1;j<=cnt;++j) ans+=((n-cnt)&1?-1LL:1LL)*f[1][id[j]];
    }
    return printf("%lld",ans),0;
}

猜你喜欢

转载自www.cnblogs.com/cjjsb/p/12240392.html
今日推荐