Sandy and Nuts

题意:

现在有一个$n$个点的树形图被拆开,现在你知道其中$m$条边,已经$q$对点的$LCA$,试求原先的树有多少种可能。

 

解法:

考虑$dp$,$f(x,S)$表示$x$的子树内的点集为$S$(不包括$x$的方案数)

$S$被拆成$S_0 ,S_1, S_2 ... S_m$,每个集合

这样考虑$LCA(a,b) =c$,与$<x,y> ∈ E$对$dp$的影响。

前者相当于$a,b$分属于两个$S_i$,

假设$x$连向的点为$y_0,y_1...$,

后者相当于不存在$S_i$中含有两个$y_i$ 且 当$S_i$中含有一个$y_i$时,必须要将$y_i$作为$S_i - \{ y_i \}$的父节点。

这样,由于每一层要背包,还要状态压缩,代码十分的复杂。

考虑类比转二叉树的方法,$f(x,S)$ 由 $f(x,S \oplus S0) \cdot f(p, S0 \oplus p)$ 转移过来。

这样代码会简化许多。

枚举子集的时候应用 $ S_0 = S \& (S_0-1)$ 的技巧。

这样,总复杂度 $O(n*3^n + q*n*2^n )$。

 

 1 #include <iostream>
 2 #include <cstdio>
 3 #include <cstring>
 4 #include <vector>
 5 
 6 #define N 14
 7 #define LL long long
 8 #define bit(x) (1<<(x))
 9 
10 using namespace std;
11 
12 int n,m,q;
13 int g[N];
14 LL f[N][1<<N];
15 vector<int> LCA[N];
16 
17 int get_bit(int S)
18 {
19     for(int i=0;i<n;i++)
20         if(S&bit(i)) return i;
21     return -1;
22 }
23 
24 int cnt_bit(int S)
25 {
26     int ans=0;
27     for(;S;S>>=1) if(S&1) ans++;
28     return ans;
29 }
30 
31 bool check(int x,int S)
32 {
33     for(int i=0;i<(int)LCA[x].size();i++)
34         if((S&LCA[x][i]) != LCA[x][i]) return 0;
35     return 1;
36 }
37 
38 LL dp(int x,int S)
39 {
40     if(f[x][S]!=-1) return f[x][S];
41     if(!S) return f[x][S] = 1;
42     f[x][S]=0;
43     int t=get_bit(S);
44     for(int S0=S;S0;S0=(S0-1)&S)
45         if(S0&bit(t))
46         {
47             bool flag=0;
48             for(int i=0;i<(int)LCA[x].size();i++)
49                 if((S0&LCA[x][i]) == LCA[x][i]){flag=1; break;}
50             if(flag || cnt_bit(g[x]&S0)>1) continue;
51             int tmp=get_bit(g[x]&S0);
52             if(tmp!=-1)
53             {
54                 if(check(tmp,S0) && ( (S0|bit(x)) & g[tmp] ) == g[tmp])
55                     f[x][S] += dp(x,S^S0)*dp(tmp,S0^bit(tmp));
56             }
57             else
58             {
59                 for(int i=0;i<n;i++)
60                     if(S0&bit(i))
61                     {
62                         if(check(i,S0) && (S0&g[i]) == g[i])
63                             f[x][S] += dp(x,S^S0)*dp(i,S0^bit(i));
64                     }
65             }
66         }
67     return f[x][S];
68 }
69 
70 int main()
71 {
72     while(~scanf("%d%d%d",&n,&m,&q))
73     {
74         for(int i=0;i<n;i++)
75         {
76             for(int j=0;j<(1<<n);j++)
77                 f[i][j]=-1;
78             g[i]=0;
79             LCA[i].clear();
80         }
81         for(int i=1,x,y;i<=m;i++)
82         {
83             scanf("%d%d",&x,&y);
84             x--,y--;
85             g[x]|=bit(y);
86             g[y]|=bit(x);
87         }
88         for(int i=1,x,y,z;i<=q;i++)
89         {
90             scanf("%d%d%d",&x,&y,&z);
91             x--,y--,z--;
92             LCA[z].push_back(bit(x)|bit(y));
93         }
94         cout << dp(0,((1<<n)-1)^1) << endl;
95     }
96     return 0;
97 }
View Code

 

转载于:https://www.cnblogs.com/lawyer/p/6591682.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值