「LOJ2091」「ZJOI2016」小星星 容斥+DP

题目描述

小 Y 是一个心灵手巧的女孩子,她喜欢手工制作一些小饰品。她有\(n\)颗小星星,用 \(m\)条彩色的细线串了起来,每条细线连着两颗小星星。有一天她发现,她的饰品被破坏了,很多细线都被拆掉了。这个饰品只剩下了\(n-1\)条细线,但通过这些细线,这颗小星星还是被串在一起,也就是这些小星星通过这些细线形成了树。小 Y 找到了这个饰品的设计图纸,她想知道现在饰品中的小星星对应着原来图纸上的哪些小星星。如果现在饰品中两颗小星星有细线相连,那么要求对应的小星星原来的图纸上也有细线相连。小 Y 想知道有多少种可能的对应方式。只有你告诉了她正确的答案,她才会把小饰品做为礼物送给你呢。

输入

第一行包含两个正整数\(n,m\),表示原来的饰品中小星星的个数和细线的条数。
接下来\(n-1\)行,每行包含两个正整数\(u,v\),表示原来的饰品中小星星\(u\)\(v\)通过细线连了起来。
这里的小星星从 开始标号。保证 ,且每对a小星星之间最多只有一条细线相连。 接下来 行,每行包含两个正整数 ,表示现在的饰品中小星星 和 通过细线连了起来。保证这些小星星通过细线可以串在一起。

输出

输出共一行,包含一个整数表示可能的对应方式的数量。
如果不存在可行的对应方式则输出\(0\)

样例

样例输入

4 3
1 2
1 3
1 4
4 1
4 2
4 3

样例输出

6

数据范围

对于所有的数据\(n\leq 17\)\(m\leq \frac{n(n-1)}{2}\)

题解

首先,我诈尸了。

第二,这是道容斥原理好题目……

考虑这个问题的等价形式,对树上的点进行重标号,让重标号的点可以嵌入原图(即,树上相邻的点在原图也相邻),并且满足每个点不会有相同的编号,然后问重标号的方案数。

于是一个简单的状压\(DP\)就呼之欲出了:\(f[i][s]\)表示以\(i\)为根的子树,把\(s\)里的数全都标号了,方案数是多少?这个可以很容易地使用子集转移做到\(O(n^k3^n)\)\(k\)随你实现的方法有所变化,然而不管\(k\)取多少都没办法过……毕竟有一个\(3^n\)在那里。

我们似乎没有办法了,不如考虑没有“两个点的标号不能相同”这个限制,这就非常好做了,\(f[i][j]\)表示以\(i\)为根的子树的答案,并且\(i\)的标号是\(j\)的方案。状态转移方程式显然很好写:

\[f[i][j]=\prod_{s\in son_i}\left(\sum_{A_{k,j}=1}f[s][k]\right)\]

然后这个东西显然会求出重复标号,我们稍微想想就知道可以容斥出来答案。\(2^n\)枚举一下哪些点不能被使用,奇数个就减掉,偶数个就加上,感性理解一下看起来是对的。很显然,强制要求一些点不选并不会增加\(DP\)难度,复杂度大约是\(O(n^32^n)\),我是不知道怎么继续优化了……用一些优化方式可以剪掉一些不必要的情况,卡卡常就过了。

\(Code:\)

#include 
#include 
#include 
#include 
#include 
using namespace std;
#define N 20
#define ll long long
int n, m, A[N][N];
int tar[N << 1], nex[N << 1], fir[N], cnt;
ll f[N][N];
vector ver[N];
void Add(int a, int b)
{
    ++cnt;
    tar[cnt] = b;
    nex[cnt] = fir[a];
    fir[a] = cnt;
}
void Dfs(int r, int fa, int s)
{
    for (int i = fir[r]; i; i = nex[i])
    {
        int v = tar[i];
        if (v != fa)
            Dfs(v, r, s);
    }
    for (int i = 1; i <= n; i++)
    {
        if (s & (1 << (i - 1)))
            continue;
        f[r][i] = 1;
        for (int j = fir[r]; j; j = nex[j])
        {
            int v = tar[j];
            if (v != fa) {
                ll sum = 0;
                int t = ver[i].size();
                for (int k = 0; k < t; k++)
                {
                    int p = ver[i][k];
                    if (s & (1 << (p - 1)))
                        continue;
                    sum += f[v][p];
                }
                f[r][i] *= sum;
            }
        }
    }
}
int main()
{
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= m; i++)
    {
        int a, b;
        scanf("%d%d", &a, &b);
        ver[a].push_back(b);
        ver[b].push_back(a);
    }
    for (int i = 1; i < n; i++)
    {
        int u, v;
        scanf("%d%d", &u, &v);
        Add(u, v), Add(v, u);
    }
    ll ans = 0;
    for (int i = 0; i < (1 << n); i++)
    {
        Dfs(1, 0, i);
        ll sum = 0;
        int bit = 1;
        for (int j = 1; j <= n; j++)
            if (i & (1 << (j - 1)))
                bit = -bit;
            else
                sum += f[1][j];
        ans += bit * sum;
    }
    printf("%lld\n", ans);
}

你可能感兴趣的:(「LOJ2091」「ZJOI2016」小星星 容斥+DP)