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}\)就是方案数,根据集合的元素取正负号即可

复杂度\(O(2^n\times n^3)\),可能需要卡常(代码是一年前的了233)

#include
#include
#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  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>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;
}

你可能感兴趣的:(BZOJ 4455: [Zjoi2016]小星星)