好题集 (13) - LG P3349 [ZJOI2016] 小星星
题意:给一棵结构固定的树和无向联通图,要求对树重新标号,使得这棵树是给定的图的子图且新的标号是一个排列,求方案数。\(n\le 17,m\le\frac{n(n-1)}{2}\)。
设 \(f_{i,j,\text{mask}}\)表示树上的 \(i\) 号点重新标号为 \(j\),子树 \(i\) 内的新编号集合为 \(\text{mask}\) 的方案数。DP 时需枚举 \(\text{mask}\) 的子集以合并子树答案,因此第二维产生的复杂度为:
然后还要枚举树上的 \(O(n)\) 条边和图里的 \(O(n^2)\) 个点对,总复杂度 \(O(n^3\cdot 3^n)\),过不了。
前面的 \(O(n^3)\) 有点难搞,所以考虑优化 \(O(3^n)\) 的底数。
尝试去掉状态定义中的 \(\text{mask}\)。\(\text{mask}\) 的本质目的是避免重复,去掉后,可以强制令所有树上点都来自于某一集合,并用容斥达到一样的效果。每次枚举 \(\{1,2,\cdots,n\}\) 的子集 \(S\),令树上所有点都是 \(S\) 中的元素而不一定用完 \(S\) 中的所有元素,然后做一遍 \(O(n^3)\) 的 DP。
没有了 \(\text{mask}\),\(f_{i,j}\) 的定义变为树上的 \(i\) 号点重新标号为 \(j\in S\) 的方案数,转移时只需枚举儿子,然后判断连边是否在图中存在,相乘累加即可。也就是说,这样转移:
设 \(g(S)\) 为枚举到的 \(S\) 的方案数,则由二项式反演得到答案:
\(g(S)\) 可以这样得出:
然后做完了。由于只枚举了树上的点集,总复杂度降到 \(O(n^3\cdot 2^n)\),就可以过了。
#ifndef ONLINE_JUDGE
#pragma GCC optimize("O3,Ofast,unroll-loops")
#endif
//#pragma GCC target("sse,sse2,sse3,ssse3,sse4,popcnt,abm,mmx,avx,avx2,tune=native")
#include<iostream>
#include<vector>
#define int long long
using namespace std;
const int N=1e3+5;
int n,m;int tot,res;
int f[N][N];
bool flg[N][N];
vector<int>vec;
namespace OIfast{
char buf[1<<21],*p1,*p2,*top,buffer[1<<21];
#define getchar()(p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<21,stdin),p1==p2)?0:*p1++)
#define gc getchar()
inline int read(){
static int n;n=0;static char c(gc);
while(!isdigit(c))c=gc;
while(isdigit(c))n=(n<<3)+(n<<1)+(c^48),c=gc;
return n;
}
}using namespace OIfast;
namespace graph{
vector<int>e[N];
inline void dfs(int u,int fa){
for(auto v:e[u]){
if(v==fa)continue ;
dfs(v,u);
}
for(auto x:vec){
f[u][x]=1;
for(auto v:e[u]){
if(v==fa)continue ;
int sum=0;
for(auto y:vec){
if(!flg[x][y])continue ;
sum+=f[v][y];
}
f[u][x]*=sum;
}
}
return ;
}
}using namespace graph;
signed main(){
n=read(),m=read();
for(int i=1;i<=m;++i){
int u=read(),v=read();
flg[u][v]=flg[v][u]=1;
}
for(int i=1;i<n;++i){
int u=read(),v=read();
e[u].push_back(v),e[v].push_back(u);
}
for(int mask=0;mask<(1<<n);++mask){
vec.clear();tot=0;
for(int i=1;i<=n;++i)if((mask>>(i-1))&1)++tot,vec.push_back(i);
dfs(1,0);for(auto x:vec)res+=((n-tot)&1?-1:1)*f[1][x];
}
return printf("%lld\n",res),0;
}
提交记录。

浙公网安备 33010602011771号