[SimpleOJ236]暴风雨

时间:2022-05-07 21:13:19

题目大意:
  给你一棵n个点的树,以及m+q条信息。
  m条描述点a到b有边直接相连。
  q条描述点a和点b的LCA为c。
  问有多少符合条件的以1为根的树。

思路:
  状压DP。
  e[i]记录需要与点i直接相连的点。
  sub[i]记录需要在点i子树中的点。
  pair[i]记录在点i不同子树下的点对(x,y),即满足lca(x,y)=i。
  f[i][j][k]表示以i为根的子树,处理完j个点,子树状压以后的状态为k。
  接下来枚举子树j的状态i和子树l的状态k,其中l是j的孩子。
  然后每次转移判断一下是否满足m+q个条件。

 #include<cstdio>
#include<cctype>
#include<vector>
#include<cstring>
typedef long long int64;
inline int getint() {
register char ch;
while(!isdigit(ch=getchar()));
register int x=ch^'';
while(isdigit(ch=getchar())) x=(((x<<)+x)<<)+(ch^'');
return x;
}
const int N=;
int e[N];//e[i]记录需要与点i直接相连的点
int sub[N];//sub[i]记录需要在点i子树中的点
std::vector<int> pair[N];//pair[i]记录在点i不同子树下的点对(x,y),即满足lca(x,y)=i
int64 f[N+][N+][<<N];//f[i][j][k]表示以i为根的子树,处理完j个点,子树状压以后的状态为k
inline void init() {
memset(e,,sizeof e);
memset(f,,sizeof f);
memset(sub,,sizeof sub);
for(register int i=;i<N;i++) {
f[i][][<<i]=;
pair[i].clear();
}
}
int main() {
for(register int T=getint();T;T--) {
init();
const int n=getint(),m=getint(),q=getint();
for(register int i=;i<m;i++) {
const int x=getint()-,y=getint()-;
e[x]|=<<y;
e[y]|=<<x;
}
for(register int i=;i<q;i++) {
const int x=getint()-,y=getint()-,lca=getint()-;
pair[lca].push_back((<<x)|(<<y));
sub[lca]|=(<<x)|(<<y);
}
for(register int i=;i<(<<n);i++) {//枚举j子树的状态
for(register int j=;j<n;j++) {//枚举j子树
if(!(i&(<<j))) continue;//j的子树不包括j显然是不可能的
if(j&&(i&)) continue;//1不可能出现在别的子树中
for(register int k=i^(<<j);k;k=(k-)&(i^(<<j))) {//枚举l子树的状态
for(register unsigned i=;i<pair[j].size();i++) {//枚举j子树中不能在同一棵子树中的点对
if((k&pair[j][i])==pair[j][i]) goto Next;//也就是说肯定不能都出现在l的子树中吧
}
for(register int l=;l<n;l++) {//枚举l子树(l是j的一个孩子)
if(!(k&(<<l))) continue;//l的子树不包括l显然是不可能的
if((k^(<<l))&e[j]) continue;//与j相连的点不可能出现在l的子树中(除了l本身)
if((e[l]&(k|(<<j)))!=e[l]) continue;//必须与l相连的点(要么是l孩子,要么是j)没有与l相连
if((sub[l]&k)!=sub[l]) continue;//必须出现在l子树中的点都在子树中
f[j][l+][i]+=f[j][l][i-k]*f[l][n][k];
}
Next:;
}
for(register int k=;k<n;k++) {
f[j][k+][i]+=f[j][k][i];
}
}
}
printf("%lld\n",f[][n][(<<n)-]);
}
return ;
}