P3349 小星星 【DP+容斥原理】
题意
给定一棵
Solution
暴力
一种朴素的想法就是暴力的枚举每一种对应方式:
如下是
1->1, 2->2, 3->3
1->1, 2->3, 3->2
1->2, 2->3, 3->1
1->2, 2->1, 3->3
1->3, 2->1, 3->2
1->3, 2->2, 3->1
然后我们对每种情况我们再用
时间复杂度:
子集枚举DP
我们由题中得出
我们发现,题中的最大难点并不是处理
所以我们得到了一个思路:在树
首先是设计状态
第一维:
第二维:
第三维:
综上所述:
呃,可能有点长,读者可以仔细地多读几遍再消化理解。
那么对于子图也就是边的这个限制条件,我们只需在DP的过程中判断
因此状态转移方程也易得出:
时间复杂度:
容斥原理优化
我们发现,上面的DP限制时间的主要因素是我们枚举了
这样导致时间暴增。
我们考虑如何去优化这个东西。
而仔细分析即可发现枚举
而这样的时间复杂度只有
但这样显然是错的。
所以我们考虑如何去减掉重复的情况。
我们发现,因为多个点对应图中的一个点。那么图中一定存在若干个点没有与树中的点对应
于是这个东西就可以容斥了:我们在图中枚举与树中对应的点有哪些,记为点集
则我们就可以通过这种方式,在DP时只需选我们枚举的点集进行状态转移即可。
时间复杂度:
Code
#include <bits/stdc++.h>
const int N = 20;
#define LL long long
int n, m, mp[N][N], c[N], len;
LL res, f[N][N];
std::vector<int> G[N];
void Dfs(int u, int fa)
{
for(int i=1;i<=len;i++) f[u][c[i]] = 1;
for(int i=0;i<G[u].size();i++)
{
int v = G[u][i];
if(v == fa) continue;
Dfs(v, u);
for(int j=1;j<=len;j++)
{
LL now = 0;
for(int k=1;k<=len;k++)
{
if(mp[c[j]][c[k]]) now += f[v][c[k]];
}
f[u][c[j]] *= now;
}
}
}
int main()
{
scanf("%d%d", &n, &m);
for(int i=1;i<=m;i++)
{
int u, v;
scanf("%d%d", &u, &v);
mp[u][v] = mp[v][u] = 1;
}
for(int i=1;i<n;i++)
{
int u, v;
scanf("%d%d", &u, &v);
G[u].push_back(v); G[v].push_back(u);
}
for(int S=1;S<(1<<n);S++)
{
memset(f, 0, sizeof(f));
len = 0; int cnt = 0;
for(int i=0;i<n;i++)
{
if(S >> i & 1) c[++len] = i + 1, cnt ++;
}
Dfs(1, 0);
LL t;
if((n - cnt) & 1) t = -1;
else t = 1;
for(int i=1;i<=len;i++) res += t * f[1][c[i]];
}
printf("%lld", res);
return 0;
}