[ARC101E]Ribbons on Tree(容斥,dp)
Description
给定一棵有 \(n\) 个节点的树,满足 \(n\) 为偶数。初始时,每条边都为白色。
现在请你将这些点两两配对成 \(\frac{n}{2}\) 个无序点对。每个点对之间的的路径都会被染成黑色
求有多少种配对方案,使得树上没有白边?
\(n\le 5000\)
Solution
法一:
树上的路径很难直接考虑。
有一种容斥的做法:记边集为 E ,枚举 T 子集中的边强制为白边,其余的不作限制, 那么:
\]
\(F(T)\) 为强制 T 的边为白边的方案数。
把 T 删掉后不难发现树变成了若干个联通块,显然这若干个连通块是独立的。
对于一个大小为 n 的连通块,两点随便配对的方案数是 \((n - 1) * (n - 3) * \cdots * 1\),记为 \(g(n)\) 。
然而暴力枚举 T 复杂度过高,考虑树型 dp ,需要知道的状态是 u 当前所在联通块大小以及容斥系数(即 T 的奇偶)。
设 \(dp[u][i][0/1]\) 为 u 子树内,u 所在联通块大小为 i ,T 的奇偶性是 0 / 1 的方案数。
转移就合并 u 的子树 v ,同时考虑 <u, v> 这条边是否选入 T 集合,有点做 01 背包的感觉。
dp[v][j][a]\times dp[u][i][b]\times g[i] \rightarrow dp'[u][i][a\oplus b\oplus 1]
\]
最后答案就是 \(|T|\) 为偶数的 - \(|T|\) 为奇数的。
\]
#include <cstdio>
#include <cstring>
#include <vector>
#include <iostream>
#include <algorithm>
using namespace std;
#define End exit(0)
#define LL long long
#define mp make_pair
#define SZ(x) ((int) x.size())
#define GO cerr << "GO" << endl
#define DE(x) cout << #x << " = " << x << endl
#define DEBUG(...) fprintf(stderr, __VA_ARGS__)
void proc_status()
{
freopen("/proc/self/status","r",stdin);
string s; while(getline(cin, s)) if (s[2] == 'P') { cerr << s << endl; return; }
}
template<typename T> inline T read()
{
register T x = 0;
register char c; register int f(1);
while (!isdigit(c = getchar())) if (c == '-') f = -1;
while (x = (x << 1) + (x << 3) + (c ^ 48), isdigit(c = getchar()));
return x * f;
}
template<typename T> inline bool chkmin(T &a,T b) { return a > b ? a = b, 1 : 0; }
template<typename T> inline bool chkmax(T &a,T b) { return a < b ? a = b, 1 : 0; }
const int maxN = 5000 + 2;
const int mod = 1e9 + 7;
vector<int> adj[maxN + 2];
int g[maxN + 2], n, size[maxN + 2];
int dp[maxN + 2][maxN + 2][2];
void input()
{
n = read<int>();
for (int i = 1; i < n; ++i)
{
int u = read<int>(), v = read<int>();
adj[u].push_back(v), adj[v].push_back(u);
}
}
void dfs(int u, int f)
{
static int tmp[maxN + 2][2];
size[u] = 1;
dp[u][1][0] = 1;
for (int v : adj[u])
if (v != f)
{
dfs(v, u);
for (int i = 0; i <= size[u]; ++i)
for (int j = 0; j <= size[v]; ++j)
for (int a = 0; a < 2; ++a)
for (int b = 0; b < 2; ++b)
{
(tmp[i + j][a ^ b] += (LL) dp[v][j][a] * dp[u][i][b] % mod) %= mod;
if (!(j & 1))
(tmp[i][a ^ b ^ 1] += (LL) dp[v][j][a] * dp[u][i][b] % mod * g[j] % mod) %= mod;
}
size[u] += size[v];
for (int i = 0; i <= size[u]; ++i)
for (int j = 0; j < 2; ++j)
dp[u][i][j] = tmp[i][j], tmp[i][j] = 0;
}
}
void solve()
{
g[0] = 1;
for (int i = 2; i <= n; i += 2) g[i] = (LL) g[i - 2] * (i - 1) % mod;
dfs(1, 0);
int ans = 0;
for (int i = 1; i <= n; ++i)
(ans += ((LL) dp[1][i][0] - dp[1][i][1] + mod) * g[i] % mod) %= mod;
cout << ans << endl;
}
int main()
{
#ifndef ONLINE_JUDGE
freopen("xhc2.in", "r", stdin);
freopen("xhc2.out", "w", stdout);
#endif
input();
solve();
return 0;
}
法二:
还是基于上面的容斥。
设 \(dp[u][i]\) 为 u 子树内还有 i 个点没有匹配,但考虑了容斥系数的答案。
合并子树后注意下 \(dp[u][0]\) 的转移要乘以 -1 的容斥系数(根除外)
#include <cstdio>
#include <cstring>
#include <vector>
#include <iostream>
#include <algorithm>
using namespace std;
#define End exit(0)
#define LL long long
#define mp make_pair
#define SZ(x) ((int) x.size())
#define GO cerr << "GO" << endl
#define DE(x) cout << #x << " = " << x << endl
#define DEBUG(...) fprintf(stderr, __VA_ARGS__)
void proc_status()
{
freopen("/proc/self/status","r",stdin);
string s; while(getline(cin, s)) if (s[2] == 'P') { cerr << s << endl; return; }
}
template<typename T> inline T read()
{
register T x = 0;
register char c; register int f(1);
while (!isdigit(c = getchar())) if (c == '-') f = -1;
while (x = (x << 1) + (x << 3) + (c ^ 48), isdigit(c = getchar()));
return x * f;
}
template<typename T> inline bool chkmin(T &a,T b) { return a > b ? a = b, 1 : 0; }
template<typename T> inline bool chkmax(T &a,T b) { return a < b ? a = b, 1 : 0; }
const int maxN = 5000 + 2;
const int mod = 1e9 + 7;
int n;
int ver[maxN << 1], nxt[maxN << 1], head[maxN + 2];
int dp[maxN + 2][maxN + 2], tmp[maxN + 2], size[maxN + 2], g[maxN + 2];
inline void Inc(int &x) { x < 0 ? x += mod : 0; }
inline void Dec(int &x) { x >= mod ? x -= mod : 0; }
void link(int u, int v)
{
static int ecnt = 0;
ver[++ecnt] = v, nxt[ecnt] = head[u], head[u] = ecnt;
}
void dfs(int u, int fa)
{
dp[u][1] = 1;
size[u] = 1;
for (int i = head[u]; i; i = nxt[i])
{
int v = ver[i];
if (v == fa) continue;
dfs(v, u);
for (int i = 0; i <= size[u]; ++i)
for (int j = 0; j <= size[v]; ++j)
Dec(tmp[i + j] += 1ll * dp[u][i] * dp[v][j] % mod);
size[u] += size[v];
for (int i = 0; i <= size[u]; ++i) dp[u][i] = tmp[i], tmp[i] = 0;
}
for (int i = 1; i <= size[u]; ++i) Inc(dp[u][0] -= 1ll * dp[u][i] * g[i] % mod);
}
int main()
{
#ifndef ONLINE_JUDGE
freopen("xhc.in", "r", stdin);
freopen("xhc.out", "w", stdout);
#endif
n = read<int>();
for (int i = 1; i < n; ++i)
{
int u = read<int>(), v = read<int>();
link(u, v), link(v, u);
}
g[0] = 1;
for (int i = 2; i <= n; ++i) g[i] = 1ll * g[i - 2] * (i - 1) % mod;
dfs(1, 0);
printf("%d\n", (mod - dp[1][0]) % mod);
return 0;
}
[ARC101E]Ribbons on Tree(容斥,dp)的更多相关文章
- ARC 101E.Ribbons on Tree(容斥 DP 树形背包)
题目链接 \(Description\) 给定一棵\(n\)个点的树.将这\(n\)个点两两配对,并对每一对点的最短路径染色.求有多少种配对方案使得所有边都至少被染色一次. \(n\leq5000\) ...
- ARC101E - Ribbons on Tree
题目链接 ARC101E - Ribbons on Tree 题解 令边集\(S \subseteq E\) 设\(f(S)\)为边集S中没有边被染色的方案数 容斥一下,那么\(ans = \sum_ ...
- HDU 5794 A Simple Chess (容斥+DP+Lucas)
A Simple Chess 题目链接: http://acm.hdu.edu.cn/showproblem.php?pid=5794 Description There is a n×m board ...
- [CF1086E]Beautiful Matrix(容斥+DP+树状数组)
给一个n*n的矩阵,保证:(1)每行都是一个排列 (2)每行每个位置和上一行对应位置不同.求这个矩阵在所有合法矩阵中字典序排第几.考虑类似数位DP的做法,枚举第几行开始不卡限制,那么显然之前的行都和题 ...
- 【BZOJ3622】已经没有什么好害怕的了 容斥+DP
[BZOJ3622]已经没有什么好害怕的了 Description Input Output Sample Input 4 2 5 35 15 45 40 20 10 30 Sample Output ...
- $bzoj2560$ 串珠子 容斥+$dp$
正解:容斥+$dp$ 解题报告: 传送门$QwQ$ $umm$虽然题目蛮简练的了但还是有点难理解,,,我再抽象一点儿,就说有$n$个点,点$i$和点$j$之间有$a_{i,j}$条无向边可以连,问有多 ...
- ARC101E Ribbons on Tree 容斥原理+dp
题目链接 https://atcoder.jp/contests/arc101/tasks/arc101_c 题解 直接容斥.题目要求每一条边都被覆盖,那么我们就容斥至少有几条边没有被覆盖. 那么没有 ...
- 【XSY3156】简单计数II 容斥 DP
题目大意 定义一个序列的权值为:把所有相邻的相同的数合并为一个集合后,所有集合的大小的乘积. 特别的,第一个数和最后一个数是相邻的. 现在你有 \(n\) 种数,第 \(i\) 种有 \(c_i\) ...
- bzoj3782上学路线(Lucas+CRT+容斥DP+组合计数)
传送门:https://www.lydsy.com/JudgeOnline/problem.php?id=3782 有部分分的传送门:https://www.luogu.org/problemnew/ ...
随机推荐
- Python代码2转3、3转2的方法
众所周知,Python2和Python3不兼容. 那么,假如遇到了超大的工作量,我们是不是需要一行一行地手动去改呢? 当然不是. (一)2转3 Python3.7(顺便提一句,现在装3.7发现好像安装 ...
- spark性能调优点(逐步完善)
1.使用高性能序列化类库2.优化数据结构3.对多次使用的RDD进行持久化/CheckPoint4.使用序列化的持久化级别5.Java虚拟机垃圾回收调优 降低RDD缓存占用空间的比例:new Spark ...
- 【shell】截取字符串前面文字
例如:有一个文件test.txt,里面有这些数据. meiguounix232 faguounix ribenunix zhongguounixtaobao hanguounixbaba 现在我想截取 ...
- .net reactor 加密混淆使用办法
https://www.cnblogs.com/bile/p/10250888.html 概述:安装了.net reactor之后,可以在安装目录下找到帮助文档REACTOR_HELP.chm,目前没 ...
- PHP开发环境 PHP培训教程
PHP开发环境 兄弟连小编整理如下: Centos安装apache,mysql,php环境 yum -y install httpd php mysql mysql-server php- ...
- BZOJ 2217: [Poi2011]Lollipop 构造 + 思维
Description 有一个长度为n的序列a1,a2,...,an.其中ai要么是1("W"),要么是2("T").现在有m个询问,每个询问是询问有没有一个连 ...
- Python3学习笔记(九):赋值,浅拷贝和深拷贝区别
一.变量赋值 在Python可变数据类型(列表,字典,集合)中,把一个可变数据类型的变量赋给另一个变量,这两个变量引用的是同一个对象,内存地址是一样的,修改当中的一个变量,另一个变量相应也会被修改 & ...
- Luogu P5469 [NOI2019]机器人 (DP、多项式)
不用FFT的多项式(大雾) 题目链接: https://www.luogu.org/problemnew/show/P5469 (这题在洛谷都成绿题了海星) 题解: 首先我们考虑,一个序列位置最右边的 ...
- Json-lib 的学习笔记
json 按照我的理解来说,就是一个字串表,可以用来表示对象的字符串,也可以用来表示数组.它比 xml 文件节省了很多标签的内容. 关于什么是 json,在这里我们就不过多介绍了. Json-lib ...
- 突破css选择器的局限,实现一个css地址选择器?
首先看一个效果,注意地址栏的变化 然后思考一下,用css如何实现? css选择器的局限 选择器是css中的一大特色,用于选择需要添加样式的元素. 选择器的种类有很多,比如 元素选择器 p {color ...