[BZOJ4455][UOJ185][Zjoi2016]小星星(树形DP+容斥)
2018-03-14 22:15
531 查看
将问题抽象化:
一个nn个节点的树,和一个nn个节点的图,要求给树上的每个节点编号,使得编号是一个11到nn的排列,并且要满足树上任意一条边(u,v)(u,v),图中一定要有边(xu,xv)(xu,xv)(xuxu表示点uu的编号),求方案数。
暴力的做法是定义状态f[i][j][S]f[i][j][S]表示节点ii编号为jj,ii的子树内的编号集合为SS的方案数。
但是这样的瓶颈在于枚举子集,复杂度是O(n3×3n)O(n3×3n)的,显然TLE。
Q:为什么要记录SS这一维?
A:要求中有「编号是一个11到nn的排列」。
尝试把「编号是一个11到nn的排列」这一条件去掉,就不用记录SS了。
这样只需要定义f[i][j]f[i][j]为在ii的子树内,点ii的编号为jj的方案数。
而这时候会出现重复编号,怎么办呢?
容斥!
先2n2n枚举{1,2,...,n}{1,2,...,n}的一个子集SS,强制规定树上每个点的编号必须是SS的子集,然后每次O(n3)O(n3)一次DP,总方案数为:
(|S|=n(|S|=n的方案数)−(|S|=n−1)−(|S|=n−1的方案数)+(|S|=n−2)+(|S|=n−2的方案数)−...)−...
复杂度降到O(n3×2n)O(n3×2n),在UOJ上需要进行一定的常数优化。
代码:
一个nn个节点的树,和一个nn个节点的图,要求给树上的每个节点编号,使得编号是一个11到nn的排列,并且要满足树上任意一条边(u,v)(u,v),图中一定要有边(xu,xv)(xu,xv)(xuxu表示点uu的编号),求方案数。
暴力的做法是定义状态f[i][j][S]f[i][j][S]表示节点ii编号为jj,ii的子树内的编号集合为SS的方案数。
但是这样的瓶颈在于枚举子集,复杂度是O(n3×3n)O(n3×3n)的,显然TLE。
Q:为什么要记录SS这一维?
A:要求中有「编号是一个11到nn的排列」。
尝试把「编号是一个11到nn的排列」这一条件去掉,就不用记录SS了。
这样只需要定义f[i][j]f[i][j]为在ii的子树内,点ii的编号为jj的方案数。
而这时候会出现重复编号,怎么办呢?
容斥!
先2n2n枚举{1,2,...,n}{1,2,...,n}的一个子集SS,强制规定树上每个点的编号必须是SS的子集,然后每次O(n3)O(n3)一次DP,总方案数为:
(|S|=n(|S|=n的方案数)−(|S|=n−1)−(|S|=n−1的方案数)+(|S|=n−2)+(|S|=n−2的方案数)−...)−...
复杂度降到O(n3×2n)O(n3×2n),在UOJ上需要进行一定的常数优化。
代码:
#include <cmath> #include <cstdio> #include <cstring> #include <iostream> #include <algorithm> using namespace std; inline int read() { int res = 0; bool bo = 0; char c; while (((c = getchar()) < '0' || c > '9') && c != '-'); if (c == '-') bo = 1; else res = c - 48; while ((c = getchar()) >= '0' && c <= '9') res = (res << 3) + (res << 1) + (c - 48); return bo ? ~res + 1 : res; } typedef long long ll; const int N = 20, M = 40; int n, m, ecnt, nxt[M], adj , go[M], tot, whi ; bool g , vis ; ll f , ans; inline void add_edge(const int &u, const int &v) { nxt[++ecnt] = adj[u]; adj[u] = ecnt; go[ecnt] = v; nxt[++ecnt] = adj[v]; adj[v] = ecnt; go[ecnt] = u; } inline void dfs(const int &u, const int &fu) { int i, j; for (int e = adj[u], v; e; e = nxt[e]) { if ((v = go[e]) == fu) continue; dfs(v, u); } for (i = 1; i <= tot; i++) { int x = whi[i]; f[u][x] = 1; for (int e = adj[u], v; e; e = nxt[e]) { if ((v = go[e]) == fu) continue; ll sum = 0; for (j = 1; j <= tot; j++) { int y = whi[j]; if (!g[x][y]) continue; sum += f[v][y]; } f[u][x] *= sum; } } } inline void solve() { int i; tot = 0; for (i = 1; i <= n; i++) if (vis[i]) whi[++tot] = i; dfs(1, 0); for (i = 1; i <= tot; i++) if (n - tot & 1) ans -= f[1][whi[i]]; else ans += f[1][whi[i]]; } inline void Dfs(const int &dep) { if (dep == n + 1) return solve(); vis[dep] = 0; Dfs(dep + 1); vis[dep] = 1; Dfs(dep + 1); } int main() { int i, x, y; n = read(); m = read(); for (i = 1; i <= m; i++) x = read(), y = read(), g[x][y] = g[y][x] = 1; for (i = 1; i < n; i++) x = read(), y = read(), add_edge(x, y); Dfs(1); cout << ans << endl; return 0; }
相关文章推荐
- [容斥 状压DP 树形DP] BZOJ 4455 [Zjoi2016]小星星 & UOJ #185 【ZJOI2016】小星星
- [BZOJ]4455: [Zjoi2016]小星星 树形DP+容斥
- BZOJ 4455 & UOJ 185 [ZJOI2016]小星星
- [BZOJ4455][Zjoi2016]小星星(容斥原理+树形DP)
- BZOJ 4455: [Zjoi2016]小星星 [容斥原理 树形DP]
- [BZOJ4455][Zjoi2016]小星星(容斥原理+树形dp)
- 【BZOJ 4455】 4455: [Zjoi2016]小星星 (容斥原理+树形DP)
- BZOJ4455 ZJOI2016小星星(容斥原理+树形dp)
- bzoj4455&UOJ185 [Zjoi2016]小星星(树形DP+容斥原理)
- bzoj 4455: [Zjoi2016]小星星 树形dp+容斥原理
- BZOJ 4455: [Zjoi2016]小星星
- bzoj 4455: [Zjoi2016]小星星
- [BZOJ4455][Zjoi2016]小星星
- 【BZOJ 4455】【UOJ #185】【ZJOI 2016】小星星
- 【BZOJ 4455】【ZJOI 2016】小星星【容斥+树型DP】
- 【UOJ】【BZOJ】 [Zjoi2016]小星星
- bzoj 4455: [Zjoi2016]小星星
- bzoj 4455: [Zjoi2016]小星星 (容斥原理+DP)
- 【bzoj4455】[Zjoi2016]小星星
- UOJ 185【ZJOI2016】小星星