ARC 086 E - Smuggling Marbles(dp + 启发式合并)
题意
Sunke 有一棵 \(N + 1\) 个点的树,其中 \(0\) 为根,每个点上有 \(0\) 或 \(1\) 个石子, Sunke 会不停的进行如下操作直至整棵树没有石子 :
- 把 \(0\) 上面的石子从树上拿走放入口袋 ;
- 把每个点上的石子移到其父亲上 ;
- 对于每个点 , 若其石子数 \(≥ 2\) , 则移除该点所有石子(不放入口袋)。
求对于所有 \(2^{N+1}\) 种放置石子的方案 , 最终 Snuke 口袋中石子数是多少 , 对 \(10^9+7\) 取模 .
\((1 \le N \le 2000) \ 400\mathrm{pts} \\ (1\le N \le 200000) \ 1000\mathrm{pts}.\)
题解
\(400\mathrm{pts}\)
我们不难发现这个操作是层层独立的... 所以我们可以考虑隔离每层来算答案
考虑一层答案对于最终的贡献 那么我们有一个显然的 \(dp\)
就是令 \(dp[u][0/1]\) 为 \(u\) 没/有 石子的方案数 (已经考虑完了 \(u\) 的子树)
我们不难发现 我们只要考虑它儿子贡献出来的方案数
我们发现有多个石子一起合并上来的方案数不好算... 所以我们就可以用所有方案数减去贡献 \(1\) 个的方案数
那么我们令 \(All\) 为所有方案数 , 就有
\]
然后我们令 \(Zero\) 为儿子全是 \(0\) 的方案数 , 就有
\]
然后又令 \(One\) 为有一个儿子为 \(1\) 的方案数 , 就有
\]
那我们就可以轻易更新当前的答案了
\]
然后每次考虑了一层后 (一开始我们只初始化了当层的答案)
我们最后要把 \(dp[0][1]\) 乘上别的层数的方案数 也就是 \(2^{n + 1- tot[dep]}\) 然后加起来就是答案了..(代码见文末)
那么 \(400\mathrm{pts}\) 就到手了qwq
然后我们考虑一下如何优化
有一个经常使用的套路 那么就是启发式合并了...
我们把儿子 \(dp\) 状态最多继承上来 然后其他的状态暴力合并上去
把别的 \(dp\) 状态暴力合并上来就行了
为了方便转移 和 空间问题 我们每个点要动态开空间
就是我们每个点开个 vector<pair<long long, long long> >
它的下标从大到小 表示 当前点向下的深度从小到大
first 代表原来的 [0] ; second 代表原来的 [1] .
然后转移的时候下标就有些细节要注意一下
然后分析一波时间复杂度qwq
其实是 \(O(n)\) 的 , 因为两个状态只会在其 \(\mathrm{LCA}\) 上合并,然后同一层两两点的 \(\mathrm{LCA}\) 只会有该层点数 \(−1\) 个。
但我需要求一个逆元 时间复杂度就变成 $O(n \log n) $ 了.... 但还是速度还行 (267ms)
那个如果用前缀积 和 后缀积 的话就可以优化成 \(O(n)\) 了 但是不想写了...
代码
\(400\mathrm{pts}\)
#include <bits/stdc++.h>
#define For(i, l, r) for(register int i = (l), i##end = (int)(r); i <= i##end; ++i)
#define Fordown(i, r, l) for(register int i = (r), i##end = (int)(l); i >= i##end; --i)
#define Set(a, v) memset(a, v, sizeof(a))
using namespace std;
inline bool chkmin(int &a, int b) {return b < a ? a = b, 1 : 0;}
inline bool chkmax(int &a, int b) {return b > a ? a = b, 1 : 0;}
inline int read() {
    int x = 0, fh = 1; char ch = getchar();
    for (; !isdigit(ch); ch = getchar()) if (ch == '-') fh = -1;
    for (; isdigit(ch); ch = getchar()) x = (x * 10) + (ch ^ 48);
    return x * fh;
}
void File() {
#ifdef zjp_shadow
	freopen ("E.in", "r", stdin);
	freopen ("E.out", "w", stdout);
#endif
}
typedef long long ll;
const ll Mod = 1e9 + 7;
const int N = 2010;
ll dp[N][2], ans = 0;
ll fpm(ll x, ll power) {
	ll res = 1;
	for (; power; power >>= 1, (x *= x) %= Mod)
		if (power & 1) (res *= x) %= Mod;
	return res;
}
int fa[N], n, dep[N], tot[N];
vector<int> G[N];
int main () {
	File();
	n = read();
	For (i, 1, n) {
		fa[i] = read();
		dep[i] = dep[fa[i]] + 1;
		G[fa[i]].push_back(i);
		++ tot[dep[i]];
	}
	++ tot[0];
	For (d, 0, n) {
		Fordown(i, n, 0) {
			if (dep[i] > d) continue ;
			if (dep[i] == d) {
				dp[i][0] = dp[i][1] = 1;
				continue ;
			}
			ll All = 1, Zero = 1;
			for (int v : G[i]) {
				(All *= (dp[v][0] + dp[v][1]) % Mod) %= Mod;
				(Zero *= dp[v][0]) %= Mod;
			}
			ll One = 0;
			for (int v : G[i])
				(One += Zero * fpm(dp[v][0], Mod - 2) % Mod * dp[v][1] % Mod) %= Mod;
			dp[i][0] = ((All - One) % Mod + Mod) % Mod;
			dp[i][1] = One;
		}
		(ans += dp[0][1] * fpm(2, n + 1 - tot[d]) % Mod) %= Mod;
	}
	printf ("%lld\n", ans);
    return 0;
}
\(1000\mathrm{pts}\)
#include <bits/stdc++.h>
#define For(i, l, r) for(register int i = (l), i##end = (int)(r); i <= i##end; ++i)
#define Fordown(i, r, l) for(register int i = (r), i##end = (int)(l); i >= i##end; --i)
#define Set(a, v) memset(a, v, sizeof(a))
using namespace std;
inline bool chkmin(int &a, int b) {return b < a ? a = b, 1 : 0;}
inline bool chkmax(int &a, int b) {return b > a ? a = b, 1 : 0;}
inline int read() {
    int x = 0, fh = 1; char ch = getchar();
    for (; !isdigit(ch); ch = getchar()) if (ch == '-') fh = -1;
    for (; isdigit(ch); ch = getchar()) x = (x * 10) + (ch ^ 48);
    return x * fh;
}
void File() {
#ifdef zjp_shadow
	freopen ("E.in", "r", stdin);
	freopen ("E.out", "w", stdout);
#endif
}
typedef long long ll;
typedef pair<ll, ll> pll;
#define fir first
#define sec second
#define mp make_pair
const ll Mod = 1e9 + 7;
const int N = 201000;
ll ans = 0;
ll fpm(ll x, ll power) {
	ll res = 1;
	for (; power; power >>= 1, (x *= x) %= Mod)
		if (power & 1) (res *= x) %= Mod;
	return res;
}
int fa[N], n, tot[N];
vector<int> G[N];
int id[N], num = 0, d[N];
vector<pll> dp[N];
ll All[N], Zero[N], One[N];
void Dfs(int u) {
	int son = n + 1;
	for (int v : G[u]) { Dfs(v); if (d[v] > d[son]) son = v;}
	if (son != n + 1) id[u] = id[son], d[u] = d[son] + 1;
	else id[u] = ++num;
	dp[id[u]].push_back(mp(1, 1));
	if ((int)G[u].size() == 1) return ;
	For (i, 0, d[u] - 1)
		All[i] = 1, Zero[i] = 1, One[i] = 0;
	int nowdep;
	for (int v : G[u])
		For (i, 0, d[v]) {
			nowdep = (d[son] - d[v]) + i;
			pll sta = dp[id[v]][i];
			(All[nowdep] *= (sta.fir + sta.sec)) %= Mod;
			(Zero[nowdep] *= sta.fir) %= Mod;
		}
	for (int v : G[u])
		For (i, 0, d[v]) {
			nowdep = (d[son] - d[v]) + i;
			pll sta = dp[id[v]][i];
			(One[nowdep] += Zero[nowdep] * fpm(sta.fir, Mod - 2) % Mod * sta.sec % Mod) %= Mod;
		}
	For (i, 0, d[u] - 1) {
		dp[id[u]][i].fir = (All[i] - One[i] + Mod) % Mod;
		dp[id[u]][i].sec = One[i];
	}
}
int dep[N];
int main () {
	File();
	n = read();
	For (i, 1, n) {
		fa[i] = read();
		G[fa[i]].push_back(i);
		dep[i] = dep[fa[i]] + 1;
		++ tot[dep[i]];
	}
	++ tot[0];
	d[n + 1] = -1;
	Dfs(0);
	For (i, 0, d[0]) {
		pll sta = dp[id[0]][i];
		(ans += sta.sec * fpm(2, n + 1 - tot[d[0] - i]) % Mod) %= Mod;
	}
	printf ("%lld\n", ans);
	return 0;
}
ARC 086 E - Smuggling Marbles(dp + 启发式合并)的更多相关文章
- BZOJ4919  [Lydsy1706月赛]大根堆  【dp + 启发式合并】
		题目链接 BZOJ4919 题解 链上的\(LIS\)维护一个数组\(f[i]\)表示长度为\(i\)的\(LIS\)最小的结尾大小 我们可以用\(multiset\)来维护这个数组,子树互不影响,启 ... 
- AtCoder Regular Contest 086 E - Smuggling Marbles(树形迭屁)
		好强的题. 方案不好算,改成算概率,注意因为是模意义下的概率所以直接乘法逆元就好不要傻傻地开double. 设$f[i][d][0]$为第i个节点离d层的球球走到第i个点时第i个点没有球的概率, $f ... 
- [2016北京集训试题7]thr-[树形dp+树链剖分+启发式合并]
		Description Solution 神仙操作orz. 首先看数据范围,显然不可能是O(n2)的.(即绝对不是枚举那么简单的),我们考虑dp. 定义f(x,k)为以x为根的子树中与x距离为k的节点 ... 
- AtCoder AGC007E Shik and Travel (二分、DP、启发式合并)
		题目链接 https://atcoder.jp/contests/agc007/tasks/agc007_e 题解 首先有个很朴素的想法是,二分答案\(mid\)后使用可行性DP, 设\(dp[u][ ... 
- P5979 [PA2014]Druzyny dp 分治 线段树 分类讨论 启发式合并
		LINK:Druzyny 这题研究了一下午 终于搞懂了. \(n^2\)的dp很容易得到. 考虑优化.又有大于的限制又有小于的限制这个非常难处理. 不过可以得到在限制人数上界的情况下能转移到的最远端点 ... 
- Codeforces 1455G - Forbidden Value(map 启发式合并+DP)
		Codeforces 题面传送门 & 洛谷题面传送门 首先这个 if 与 end 配对的结构显然形成一个树形结构,考虑把这棵树建出来,于是这个程序的结构就变为,对树进行一遍 DFS,到达某个节 ... 
- [多校 NOIP 联合模拟 20201130 T4] ZZH 的旅行(斜率优化dp,启发式合并,平衡树)
		题面 题目背景 因为出题人天天被 ZZH(Zou ZHen) 吊打,所以这场比赛的题目中出现了 ZZH . 简要题面 数据范围 题解 (笔者写两个log的平衡树和启发式合并卡过的,不足为奇) 首先,很 ... 
- 2018.10.14 loj#516. DP 一般看规律(启发式合并)
		传送门 注意到一种颜色改了之后就不能改回去了. 因此可以启发式合并. 每次把小的合并给大的. 这样每个数最多被合并logloglog次. 如果维护一棵比较下标的平衡树的话,对于答案有贡献的就是每个数与 ... 
- 银河战舰 [启发式合并+dp]
		题面 思路 我们首先考虑传统的链上LIS做法:保存每个长度的LIS末端的最小值,二分查找 那么这道题其实就只是搬到树上来做了而已 我们考虑一个节点,假设它的儿子已经处理完毕了 那么我们选择LIS最长的 ... 
随机推荐
- ps昏暗室内照片调成暖色光亮效果
			最终效果 一.打开素材图片,把背景图层复制一层,做HDR滤镜操作,如果你没有这款滤镜,可以去网上下载,参数及效果如下图. 二.复制一层,用Noise滤镜做降噪处理,参数及效果如下图. 三.新建一个图层 ... 
- static特别用法【静态导包】——Java包的静态导入
			面试我问你static关键字有哪些作用,如果你答出static修饰变量.修饰方法我会认为你合格,答出静态块,我会认为你不错,答出静态内部类我会认为你很好,答出静态导包我会对你很满意,因为能看出你非常热 ... 
- BOM、DOM
			window对象 所有浏览器都支持window对象,他表示浏览器窗口. 全局变量是window对象的属性.全局函数是window对象的方法. window的常用方法: window.innerHeig ... 
- Mongo安装与使用
			MongoDB[1] 是一个基于分布式文件存储的数据库.由C++语言编写.旨在为WEB应用提供可扩展的高性能数据存储解决方案. mongoDB MongoDB[2] 是一个介于关系数据库和非关系数 ... 
- python内涵段子爬取练习
			# -*- coding:utf-8 -*-from urllib import request as urllib2import re# 利用正则表达式爬取内涵段子url = r'http://ww ... 
- Oracle通过ROWID删除表中重复记录
			-- 1 通过ROWID删除T1表里重复的记录 SELECT ROWID,A,B--DELETE FROM T1WHERE ROWID IN ( SELECT RD FROM ( ... 
- spring初始化bean时执行某些方法完成特定的初始化操作
			在项目中经常会在容器启动时,完成特定的初始化操作,如资源文件的加载等. 一 实现的方式有三种: 1.使用@PostConstruct注解,该注解作用于void方法上 2.在配置文件中配置init-me ... 
- Flutter之Container详解
			1 基本内容1.1 继续关系Object > Diagnosticable > DiagnosticableTree > Widget > StatelessWidget &g ... 
- 当mysql报错1045时的解决方法
			2.用记事本打开 添加 打开后,搜索mysqld关键字 找到后,在mysqld下面添加skip-grant-tables,保存退出. 如果保存在了c盘里不能修改那么就采用这样的方法 然后就可以修改c盘 ... 
- Prism框架中加载类库中时其中第三方类dll提示无法加载程序集
			Prism框架是采用一种依赖注入的方式动态加载程序集,能够在程序需要加载的时候将程序集注入到里面去,实现程序的热插拔效果,而且采用这种框架能够让我们进行一个大项目的独立开发,在最近的一个项目中在独立开 ... 
