抱歉,您的浏览器无法访问本站

本页面需要浏览器支持(启用)JavaScript


了解详情 >

题目传送门

题意

有一棵\(n\)个节点的以1为根的树,有\(m\)条边已知,并且有\(q\)个限制\(a_i,b_i,c_i\),需要满足\(\mathrm{LCA}(a_i,b_i)=c_i\)。求满足条件的树的数量。

\(m< n\le 13,q\le 100\)

题解

树形状压DP。DP状态很显然,\(dp_{u,mask}\)表示以\(u\)为根,由\(mask\)这些点组成的子树的方案数。\(mask\)是一个二进制状态。

为方便讨论,以下题解和代码节点编号从\(0\)开始。

转移方程

\[dp_{u,mask}=\sum dp_{v,submask}\times dp_{u,mask\oplus submask}\]

\(\oplus\)表示的是异或(\(xor\))运算。

但是,我们直接枚举\(v,submask\)会有重复,例如一棵二叉树,根为\(root\),左右儿子分别为\(leftson,rightson\)。当枚举\(v=leftson\)时会计算这棵树,\(v=rightson\)时又会计算这棵树,就会出现重复。

所以,我们规定一个点\(pos\in mask\ (pos\ne u)\),强制\(pos\)\(submask\)中才能转移。

转移条件

题目中有两种限制条件,分别为边和\(\mathrm{LCA}\)

  1. 对于\(\mathrm{LCA}\)的限制: 1.1. 对于限制\((a,b,c)\),如果\(c=u\),但是\(a,b\in submask\),那么\(\mathrm{LCA}\)一定不为\(c\),不满足条件。 1.2. 对于限制\((a,b,c)\), 如果\(c\in submask\),但\(a,b\)中有至少一个不在\(submask\)中,则\(\mathrm{LCA}\)一定不为\(c\),不满足条件。
  2. 对于边的限制: 2.1. 对于边\((x,y)\),如果\(x,y\ne u\),但是\(x,y\)其中一个在\(submask\)中,另一个不在,则这条边不可能在树上,不满足条件。 2.2. 如果\(u\)\(i\)有边且\(i\in submask\)\(i\)的数量大于1,则不可能有满足条件的树,不满足条件。

在2.2中,如果这样的\(i\)的数量等于1,则转移时\(v\)不用枚举,\(v\)只能是那个\(i\)。否则\(v\)需要在\(submask\)中枚举。

关于复杂度

子集枚举可以用for (register int submask = mask; submask; submask = (submask - 1) & mask)

此时复杂度并不是\(O(4^n)\),而是\(O(3^n)\),因为每次枚举到的\(submask\)一定是\(mask\)的子集。状态数为\(3^n\)

因为此时\(n\)个数有三种状态:不在\(mask\)中,在\(mask\)但不在\(submask\)中,在\(submask\)中。所以是\(3^n\)

所以复杂度为\(O(3^nn(n+q+m))\)

观察代码可以发现这个复杂度非常不满,很多状态和子集是没用的。加上CF的评测机速度,还是可以过的。

代码

Code 实测93ms

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
#include <cstdio>
#include <cstring>
int n, m, q, edge[15][15];
struct Edge{ int x, y; } E[15];
struct node{ int x, y, z; } a[105];
long long dp[15][100005];
bool in(int x, int s){ return s & (1 << x); }
long long dfs(int u, int mask){ // 用记搜实现
long long &res = dp[u][mask];
if (~res) return res;
res = 0, mask -= 1 << u;
int pos;
for (pos = 0; pos < n; ++pos) if (in(pos, mask)) break; // 强制pos在submask中
for (register int submask = mask; submask; submask = (submask - 1) & mask) // 枚举子集
if (in(pos, submask)){
int flag = 1, v, cnt = 0;
// 条件1.1
for (register int i = 1; i <= q; ++i)
if (a[i].z == u && in(a[i].x, submask) && in(a[i].y, submask))
{ flag = 0; break; }
if (!flag) continue;
// 条件1.2
for (register int i = 1; i <= q; ++i)
if (in(a[i].z, submask) && (!in(a[i].x, submask) || !in(a[i].y, submask)))
{ flag = 0; break; }
if (!flag) continue;
// 条件2.1
for (register int i = 1; i <= m; ++i)
if (E[i].x != u && E[i].y != u && (in(E[i].x, submask) ^ in(E[i].y, submask)))
{ flag = 0; break; }
if (!flag) continue;
// 条件2.2
for (register int i = 0; i < n; ++i)
if (edge[u][i] && in(i, submask)){ ++cnt; v = i; }
if (cnt > 1) continue;
if (cnt == 1) res += dfs(v, submask) * dfs(u, mask ^ submask ^ (1 << u));
else{
for (v = 0; v < n; ++v)
if (in(v, submask)) res += dfs(v, submask) * dfs(u, mask ^ submask ^ (1 << u)); // 转移方程
}
}
return res;
}
int main(){
scanf("%d%d%d", &n, &m, &q);
for (register int i = 1; i <= m; ++i)
scanf("%d%d", &E[i].x, &E[i].y), --E[i].x, --E[i].y, // 编号从0开始
edge[E[i].x][E[i].y] = edge[E[i].y][E[i].x] = 1;
for (register int i = 1; i <= q; ++i)
scanf("%d%d%d", &a[i].x, &a[i].y, &a[i].z), --a[i].x, --a[i].y, --a[i].z; // 同上
memset(dp, -1, sizeof dp);
for (register int i = 0; i < n; ++i) dp[i][1 << i] = 1; // 初始化
printf("%lld", dfs(0, (1 << n) - 1));
}

评论