problem
luogu-P3349
solution
这个数据首先就能想到状压 dpdpdp。
先考虑在树上的朴素 dp(i,j,S):dp(i,j,S):dp(i,j,S): 节点 iii 的对应原图编号为 jjj,其子树对应的编号构成的点集为 SSS 的方案数。
需要满足两个限制条件:
- 原图的每个节点只能被编一次号,且必须被编号。
- 树上每一条边必须在原图出现。
第二个限制是对转移后继的限制,重点在于第一个限制。
先假设没有第一个限制,我们就可以扔掉状压的第三维,即:
dp(u,i)=∏v∈sonu∑j=1ndp(v,j)dp(u,i)=\prod_{v\in son_u}\sum_{j=1}^ndp(v,j) dp(u,i)=v∈sonu∏j=1∑ndp(v,j)
在此基础上,我们再考虑加上第一条限制。
当原图的一个点被多个树上点对应编号时,那么一定有其他的点没有被对应编号。
所以我们可以每次枚举有哪些点没有被对应编号。
最后容斥即可,(∣S∣=n)−(∣S∣=n−1)+(∣S∣=n−2)...(|S|=n)-(|S|=n-1)+(|S|=n-2)...(∣S∣=n)−(∣S∣=n−1)+(∣S∣=n−2)... 000 个没有−1-1−1个没有 +2+2+2 个没有…\dots…
总时间复杂度 O(n2n)O(n2^n)O(n2n)。
code
#include <bits/stdc++.h>
using namespace std;
#define maxn 20
#define int long long
vector < int > G[maxn];
int g[maxn][maxn];
int dp[maxn][maxn];
int flag[maxn];
int n, m;void dfs( int u, int fa ) {for( int i = 1;i <= n;i ++ ) dp[u][i] = flag[i] ^ 1;for( int v : G[u] )if( v == fa ) continue;else {dfs( v, u );for( int i = 1;i <= n;i ++ ) {if( flag[i] ) continue;int sum = 0;for( int j = 1;j <= n;j ++ )if( g[i][j] and ! flag[j] ) sum += dp[v][j];dp[u][i] *= sum;}}
}signed main() {scanf( "%lld %lld", &n, &m );for( int i = 1, u, v;i <= m;i ++ ) {scanf( "%lld %lld", &u, &v );g[u][v] = g[v][u] = 1;}for( int i = 1, u, v;i < n;i ++ ) {scanf( "%lld %lld", &u, &v );G[u].push_back( v );G[v].push_back( u );}int ans = 0;for( int s = 0;s < (1 << n);s ++ ) {for( int i = 0;i < n;i ++ )flag[i + 1] = s >> i & 1;dfs( 1, 0 );int sum = 0;for( int i = 1;i <= n;i ++ ) sum += dp[1][i];if( __builtin_popcount( s ) & 1 ) ans -= sum;else ans += sum;}printf( "%lld\n", ans );return 0;
}