嘘~ 正在从服务器偷取页面 . . .

洛谷P3349 [ZJOI2016]小星星 题解


洛谷P3349 [ZJOI2016]小星星 题解

题目链接:P3349 [ZJOI2016]小星星

题意

cxy 是一个心灵手巧的女孩子,她喜欢手工制作一些小饰品。她有 $n$ 颗小星星,用 $m$ 条彩色的细线串了起来,每条细线连着两颗小星星。

有一天她发现,她的饰品被破坏了,很多细线都被拆掉了。这个饰品只剩下了 $n-1$ 条细线,但通过这些细线,这颗小星星还是被串在一起,也就是这些小星星通过这些细线形成了树。cxy 找到了这个饰品的设计图纸,她想知道现在饰品中的小星星对应着原来图纸上的哪些小星星。如果现在饰品中两颗小星星有细线相连,那么要求对应的小星星原来的图纸上也有细线相连。cxy 想知道有多少种可能的对应方式。

只有你告诉了她正确的答案,她才会把小饰品做为礼物送给你呢。

输入格式

第一行包含 $2$ 个正整数 $n,m$,表示原来的饰品中小星星的个数和细线的条数。

接下来 $m$ 行,每行包含 $2$ 个正整数 $u,v$,表示原来的饰品中小星星 $u$ 和 $v$ 通过细线连了起来。这里的小星星从 $1$ 开始标号。保证 $u\neq v$,且每对小星星之间最多只有一条细线相连。

接下来 $n-1$ 行,每行包含 $2$ 个正整数 $u,v$,表示现在的饰品中小星星 $u$ 和 $v$ 通过细线连了起来。保证这些小星星通过细线可以串在一起。

输出格式

输出共 $1$ 行,包含一个整数表示可能的对应方式的数量。

如果不存在可行的对应方式则输出 0

数据范围

对于 $100\%$ 的数据,$n\leq 17$,$m\leq \frac 12n(n-1)$。

子集反演+dp+容斥计数题。

题目的意思其实是:

给定一棵 $n$ 个结点的树 $T$ 和一个 $n$ 个结点的无向图 $G=(V,E)$

要求给树上的结点编号使得编号是一个 $1\sim n$ 的排列,并且任意 $(u,v) \in T$ 有 $(u,v) \in E$ 。

在本题中直接状压是过不了的,因为复杂度是 $\mathcal{O}(n^3\times 3^n)$ 的

因此考虑设 $f_{i,j}$ 表示结点 $i$ 编号为 $j$ , $i$ 所在子树的方案数。

但是不记录 $i$ 所在子树的编号状况,可能存在重复编号的情况,因此考虑容斥。

我们可以先枚举一个集合 $S = [n]$ (即 $S \subseteq \{1,2,\cdots,n\}$ ),然后钦定选择的结点必须属于 $S$

则总方案数为

其中 $|N(S)|$ 指方案数,我们可以在 $\mathcal{O}(n^3)$ 的复杂度内完成。

证明: 咕咕咕。

时间复杂度 $\mathcal{O}(n^32^n)$

代码:(不知道为什么题目不用取模)

#include <bits/stdc++.h>
using namespace std;
#define int long long
#define INF 0x3f3f3f3f3f3f3f3f
void up(int &x,int y) { x < y ? x = y : 0; }
void down(int &x,int y) { x > y ? x = y : 0; }
#define N ((int)(20))

bitset<N> vis;
int n,m,tot,res,pos=1,c[N],f[N][N],g[N][N],head[N];
struct Edge{ int u,v,next; }e[N*2];
void addEdge(int u,int v) { e[++pos] = {u,v,head[u]}; head[u]=pos; }
void dfs(int u,int fa)
{
    for(int i=1; i<=tot; i++) f[u][c[i]] = 1;
    for(int i=head[u],sum; i; i=e[i].next)
    {
        int v = e[i].v;
        if(v == fa) continue; dfs(e[i].v,u);
        for(int j=1; j<=tot; j++)
        {
            int x = c[j], y;  sum = 0;
            for(int k=1; k<=tot; k++)
                g[x][y = c[k]] ? (sum += f[v][y]) : 0;
            f[u][x] *= sum;
        }
    }
}
void work()
{
    tot = 0;
    for(int i=1; i<=n; i++) if(vis[i]) c[++tot] = i;
    dfs(1,0);
    for(int i=1; i<=tot; i++)
        res += (((n - tot) & 1) ? -f[1][c[i]] : f[1][c[i]]);
}
void solve(int u)
{
    if(u == n + 1) return work();
    vis[u] = 0; solve(u+1); vis[u] = 1; solve(u+1);
}
signed main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);cout.tie(0);
    // freopen("check.in","r",stdin);
    // freopen("check.out","w",stdout);
    cin >> n >> m;
    for(int i=1,u,v; i<=m; i++)
        { cin >> u >> v; g[u][v] = g[v][u] = 1; }
    for(int i=1,u,v; i<n; i++)
        { cin >> u >> v; addEdge(u,v); addEdge(v,u); }
    solve(1); cout << res << '\n';
    return 0;
}

文章作者: q779
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-ND 4.0 许可协议。转载请注明来源 q779 !
评论
  目录